一、加密方式
AES256分组对称加密是指将明文数据分解为多个 16 字节的明文块,利用密钥分别对每个明文块进行加密,得到相同个数的 16 字节密文块,如下图所示:
如果分解后有明文块不足 16 字节,就需要涉及填充和链加密模式。
二、填充方式
由于对明文数据进行了分块,那么就有可能存在分解后的明文块不足 128 位的情况,这就需要对明文块进行填充。
AES加密支持多种填充方式:NoPadding,PKCS5Padding,ZerosPadding,PKCS7Padding。
其中 NoPadding 表示不进行填充,这就需要自行检测和控制数据的长度;ZerosPadding 表示用 0 填充缺少的位数;
PKCS5Padding 和 PKCS7Padding 的填充方式在实际效果中是相同的,用缺少的位数进行填充,如块长度为 11 个字节,缺少 5 个字节,那么就填充 5 个字节的内容,每个字节的内容为十进制的 5,如下图所示:
→
注意:如果块长度刚好为 16 字节,则需要在块后补 16 个字节的数据,每个字节数据为十进制的 16,如下所示:
ENCChaincode使用了 PKCS7Padding 填充方式。
三、加密模式
ECB 模式:
CBC 模式(密码分组连接模式):
CFB 模式(密码反馈模式):
OFB 模式(输出反馈模式):
ENCChaincode使用了 CBC(密码分解连接)模式。
四、源码解读
ENCKEY 和 DECKEY:用于分组对称加密和解密
命令 openssl rand -base64 32
ENCKEY=DECKEY=’hJM2KYj33vBq/+3GGybwyFB3chOkNo4lv1swAEMxC3E=’
IV:加密过程中使用的向量,ENCCC 例子使用了 CBC 模式,IV 需要提供给链码,若不提供则会随机生成,但如果背书策略指定需要由多个节点进行背书,那么就必须提供唯一的 IV,否则节点在背书时会出现读写冲突。
命令 opensslrand -base64 16
IV=’ST56TUR9CYY+NZ41sorYVg==’
命令 openssl ecparam -nameprime256v1 -genkey | tail -n5 | base64 -w0
SIGKEY=VERKEY=’LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUtKRzhzMnlqNzJEcGo3L0o1OHFHQzdHa1R5cXQ1REkwZWJod01GamkxMVZvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMWlEOUlyU1Ixdi9LOVN6TzVCSjVaUUpIZVdTblIxbE00b21iRTBwZ0NpUml6ZExtZkkyVgp6VE84ZE5PTHNhYlhCZGZyNHJUWDNhSkxzeHE5azBZUm1nPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=’
链码源文件:fabric/example/chaincode/go/enccc_example/enccc_example.go
(一)Encrypter
作用:把通过AES256位密钥进行加密的 value 写入账本。
源码分析:
1. 创建加密实体,参数为:ID,bccp 实例,加密密钥 encKey,IV(可选参数,若不提供则随机创建)
ent, err:= entities.NewAES256EncrypterEntity(“ID”, t.bccspInst, encKey, IV)
2. 加密和写入数据
err= encryptAndPutState(stub, ent, key, cleartextValue)
2.1 使用加密器实体对 value 进行加密
ciphertext, err := ent.Encrypt(cleartextValue)
2.2 将加密后的 value 写入账本
stub.PutState(key, ciphertext)
(二)Decrypter
作用:将账本 value 读出并通过AES256位密钥进行解密。
源码分析:
1. 创建加密实体,参数为:ID,bccp 实例,加密密钥 encKey,IV(可选参数,此处示例 readme 文档中描述:若不提供则随机创建,但与 IV 的数据结构描述不符)
ent, err:= entities.NewAES256EncrypterEntity(“ID”, t.bccspInst, encKey, IV)
2. 读取和解密
err= getStateAndDecrypt (stub, ent, key, cleartextValue)
2.1 读出 key 对应的 value
ciphertext, err := stub.GetState(key)
2.2 对 value 进行解密
ent.Decrypt(ciphertext)
(三)EncrypterSigner
作用:对 value 使用AES256位密钥进行加密和 prime256v1 标准进行签名,并将 value 写入账本。
源码分析:
1. 创建加密签名实体,参数为:ID,bccp 实例,加密密钥 encKey,签名密钥 sigKey
ent, err:= entities.NewAES256EncrypterECDSASignerEntity(“ID”, t.bccspInst,encKey, sigKey)
2. 对签名数据加密,写入账本
err= signEncryptAndPutState(stub, ent, key, cleartextValue)
2.1 创建一个签名消息,value 作为 Payload,加密签名实体的 ID 作为 ID,签名消息数据结构如下图所示:
msg := &entities.SignedMessage{Payload: value,ID: []byte(ent.ID())}
2.2 利用加密签名实体对 SignedMessage 进行签名,签名后的信息作为签名消息的 Sig 字段
err := msg.Sign(ent)
2.3 序列化 SignedMessage
b, err := msg.ToBytes()
2.4 对序列化的签名信息进行加密并写入账本
encryptAndPutState(stub, ent, key, b)
(四)DecrypterVerify
作用:从账本中读取数据,使用 AES256 位密钥进行解密和 prime256v1 标准验证签名。
源码分析:
1. 创建加密签名实体,参数为:ID,bccp 实例,加密密钥 encKey,签名密钥 sigKey
ent, err:= entities.NewAES256EncrypterECDSASignerEntity(“ID”, t.bccspInst,encKey, sigKey)
cleartextValue, err :=getStateDecryptAndVerify(stub, ent, key)
2.1 读取账本数据并解密
val, err := getStateAndDecrypt(stub, ent, key)
2.2 创建 SignedMessage 对象 msg
msg := &entities.SignedMessage{}
将序列化的 val 解码至 msg 中
err = msg.FromBytes(val)
2.3 验证签名
ok, err := msg.Verify(ent)
五、链码测试
使用 e2e_cli 示例网络结构:一个 orderer 节点,分属两个 Org 的四个 peer 节点。
修改了 script.sh 脚本,在所有 peer 节点安装、实例化 ENCCC(ENC Chaincode)
测试过程中的 ENCKEY、DECKEY、DECKEY1、IV、SIGKEY、VERKEY、VERKEY1 已经设置为 cli 容器的环境变量
Test1:验证加密解密
使用 ENCKEY 将 value 写入账本:
peerchaincode invoke -o orderer.example.com:7050 –tls –cafile $ORDERER_CA -C $CHANNEL_NAME -n enccc -c'{“Args”:[“ENCRYPT”,”a”,”100″]}’–transient”{\”ENCKEY\”:\”$ENCKEY\”,\”IV\”:\”$IV\”}
结果:数据可正确加密写入
使用 DECKEY 读取 value:
peer chaincode query -C $CHANNEL_NAME -n enccc -c ‘{“Args”:[“DECRYPT”,”a”]}’ –transient “{\”DECKEY\”:\”$DECKEY\”}”
结果:数据正确读出并解密
使用 DECKEY1 读取 value:
peer chaincode query -C $CHANNEL_NAME -n enccc -c ‘{“Args”:[“DECRYPT”,”a”]}’ –transient “{\”DECKEY\”:\”$DECKEY1\”}”
结果:由于 DECKEY1 not equal ENCKEY,导致解密失败
Test2:验证签名验证
使用 ENCKEY 和 SIGKEY 将 value 写入账本:
peer chaincode invoke -o orderer.example.com:7050 –tls –cafile $ORDERER_CA -C $CHANNEL_NAME -n enccc -c ‘{“Args”:[“ENCRYPTSIGN”,”b”,”200″]}’ –transient “{\”ENCKEY\”:\”$ENCKEY\”,\”SIGKEY\”:\”$SIGKEY\”}”
结果:数据正确签名加密,写入账本
使用 ENCKEY 和 VERKEY 读取 value:
peer chaincode query -C $CHANNEL_NAME -n enccc -c ‘{“Args”:[“DECRYPTVERIFY”,”b”]}’ –transient “{\”DECKEY\”:\”$DECKEY\”,\”VERKEY\”:\”$VERKEY\”}”
结果:数据正确读出并解密,验证签名通过
使用 ENCKEY 和 VERKEY1 读取 value:
peer chaincode query -C $CHANNEL_NAME -n enccc -c ‘{“Args”:[“DECRYPTVERIFY”,”b”]}’ –transient “{\”DECKEY\”:\”$DECKEY\”,\”VERKEY\”:\”$VERKEY1\”}”
结果:数据正确读出并解密,签名验证不通过
Test3:验证多节点背书情况下 IV 的使用
测试组织结构如下:
Org1:peer0,peer1
Org2:peer0,peer1
背书策略指定需要 Org1 和 Org2 各一个 peer 进行背书,在不提供 IV 的情况下,可正常运行。
示例程序 enccc_example 的 readme 文档中描述 IV 是 AES256 分组对称加密的初始化向量,在不提供时会随机创建,导致最终的加密结果不同,多节点背书验证时会无法通过验证,但源码中对 IV 的描述为:只有不为空时才使用 IV。
六、总结
enccc_example 作为一个示例链码,为我们提供了一种对账本数据进行加密的解决方案,若要通过加密对数据进行隔离,则需要针对业务进行一定的修改,目前还在根据业务探索如何使用加密进行数据隔离,大家有好的想法阔以私信在下。
签名和验证部分采用了 prime256v1 标准,但通过测试发现签名和验证需要使用相同的 key,具体的签名和验证处理需要进一步研读源码,目前在业务中还未找到此种方式的具体应用场景。
参考资料:对称加密和分组加密中的四种模式(ECB、CBC、CFB、OFB)
注:文中图片来源于网络,如有侵犯请联系在下删除