AES-ECC数字信封协议与接口文档
-
AES-ECC数字信封协议文档
概述
AES-ECC数字信封加密算法库,是基于AES256,以及椭圆曲线secp256k1实现的对任意字节序列实现数字信封加密的算法库。
对数据使用AES对称密钥$key$进行加密,再通过椭圆曲线算法,使用公钥$pubKey$加密$key$,实现数据加密。解密过程与之相反,通过公钥对应的私钥$priKey$解密,得到对称密钥$key$,再通过$key$解密得到原始数据。
AES算法说明
AES(Advanced Encryption Standard,高级加密标准),是一种常见的加密算法,采用对称分组密码体制。本算法库采用的是AES-256-CBC-PKCS7Padding加密方式,此种方式下加解密需要一个16字节的初始化向量(Initialzation Vector, IV),同时密钥的长度为256位,即32字节;并采用CBC模式加密,即先将明文切分成若干个小段,每一个小段与初始块或上一段的密文进行异或运算后,再与密钥进行加密;采用PKCS7Padding填充模式,保证明文分段时,对于数据不齐时,对数据按字节进行补齐,若数据本身已经对齐,则会填充一块长度为块大小的数据,每个字节都是块大小。
算法具体描述如下:
明文分段
对于任意长度的明文$Plaintext$,首先根据PKCS7Padding进行补齐,补齐后将数据分段,每块大小为16字节。分段后得到n个明文段,即$Plaintext_1,...,Plaintext_n$。
加密数据
- $Plaintext_1$与IV进行异或运算,得到$Input_1$做为AES加密算法的输入,与密钥$key$进行计算,得到密文$Ciphertext_1$。
- $Plaintext_2$与$Ciphertext_1$进行异或运算,得到$Input_2$做为AES加密算法的输入,与密钥$key$进行计算,得到密文$Ciphertext_2$。
- 循环往复,直至将所有的明文段加密完,最后一次得到密文$Ciphertext_n$。
- 将$Ciphertext_1,...,Ciphertext_n$拼接在一起,即为最终的密文$Ciphertext$。
解密数据
- 将$Ciphertext$按16字节,划分成n个密文段$Ciphertext_1,...,Ciphertext_n$。
- $Ciphertext_1$与密钥$key$进行解密运算,得$Output_1$,$Output_1$与IV进行异或,得到第一段明文$Plaintext_1$。
- $Ciphertext_2$与密钥$key$进行解密运算,得到$Output_2$,$Output_2$与$Ciphertext_1$进行异或,得到第二段明文$Plaintext_2$。
- 循环往复,即可得到左右的明文段,最后将$Plaintext_1,...,Plaintext_n$拼接在一起,即可得到最终的明文$Plaintext$。
ECC算法说明
ECC(Elliptic Curve Cryptography,椭圆曲线密码学),基于有限域的椭圆曲线和复杂的椭圆曲线离散对数,实现的一种非对称加密系统。
在本算法库中的椭圆曲线加密算法是基于secp256k1实现的。 大多数常用的曲线都具有随机结构,但 secp256k1 是以特殊的非随机方式构建的,因此可以实现特别高效的计算。因此,如果实现充分优化,它通常比其他曲线快 30% 以上。
而本算法库所实现的是基于椭圆曲线的一种集成加密方案,ECIES(elliptic curve integrate encrypt scheme)。其中密钥派生函数、校验码的生成算法和对称加密方案是根据需要设计的,本算法库选择的密钥派生函数为SHA512哈希函数,校验码生成算法为SHA256哈希函数,对称加密方案选择的是AES/CBC/PKCS7Padding。
密钥生成
在椭圆曲线$Ep(a,b)$中选择两个点$G,K$,满足$K=kG$,$n$为$G$的阶$(nG=O_{\infty})$,$k$为小于$n$的整数。则给定$k$和$G$,计算$K$是很容易的,而给定$K$和$G$计算$k$是非常困难的,同时由于$n,p$的取值是非常大的,因此想要把各个点逐一算出是不可能的,基于此构建非对称密码算法。
- 在椭圆曲线上随机选取一点$G$,称为基点。
- $k(k<n)$为私钥。
- $K$为公钥。
在使用椭圆曲线进行加密时,用户需要公开自己的公钥$K$,其他人可以通过公钥加密明文数据得到密文发送给用户,而用户可以通过自己的私钥$k$进行解密。而其他人在不知道私钥的情况下是无法解密的。
加密算法
为加密信息$m$,需要以下步骤:
- 生成一个随机数$r\in[1,n-1]$,并计算$R=rG$。
- 将秘密$S$映射到椭圆曲线上,令$S=P_x$,其中$P=(P_x, P_y)=rK(P\neq O_{\infty})$。
- 使用哈希算法SHA512,对秘密$S$进行哈希运算,得到的值分别做为对称加密的密钥以及校验密文是否正确的密钥,$k_E||k_M=\mathsf{SHA512}(S)$。
- 对秘密进行加密$c=E(k_E;m)$
- 计算加密信息的校验码$d=\mathsf{SHA256}(k_M;c)$
- 输出$R||c||d$。
解密算法
当获得$R||c||d$后执行解密:
- 求出$P=(P_x,P_y)=kR$,因为$P=kR=krG=rkG=rK$,其中$S=P_x$。
- 生成加密密钥和校验密钥$k_E||k_M=\mathsf{SHA512}(S)$。
- 计算校验码是否正确,即$d$与$\mathsf{SHA256}(k_M;c)$。
- 使用对称密钥进行解密$m=D(k_E;c)$。
AES-ECC SDK 接口文档
通过java和JS两种语言,实现了数字信封的SDK,并且两种语言的SDK可以实现互相加解密。
对于明文采用AES进行加密,对加密中使用的对称密钥,利用椭圆曲线中的ECIES进行加密(公钥加密),加密后得到关于明文的AES密文,以及关于对称密钥的ECIES密文。解密的过程与此相反,通过ECIES进行解密(私钥解密),得到AES的对称密钥,利用此密钥对AES的密文进行解密,得到明文。
JAVA SDK
接口说明
public class envelop{}
将数字信封封装在
envelop
中。通过实例化envelop
对象,完成数字信封的相关功能。public void setSymKey(String key)
传入AES的对称密钥。
public void setEciesPubKey(String pk) public void setEciesSecKey(String sk)
传入ECC的公钥/私钥。
public String getSymKey()
获取当前AES的密钥。
public String getEciesPubKey() public String getEciesSecKey()
获取当前ECC的公钥/私钥。
public String encryptMsg(String msg) throws Exception public String decryptMsg(String encMsg) throws Exception
使用AES对明文进行加密,返回密文;以及使用AES对密文进行解密,返回解密后的明文。
public String encryptKey(String key) throws Exception public String decryptKey(String encKey) throws Exception
使用ECC中的ECIES加密算法,对明文(在数字信封中为AES的对称密钥)进行加密,返回密文(在数字信封中为AES对称密钥的密文);以及使用ECIES解密算法对密文进行解密,返回明文。
具体实例
实例化对象
envelop env = new envelop();
实例化一个数字信封对象,此对象会通过构造函数,分配AES加密的密钥以及ECC的公私钥对。
数字信封加密
String encMsg = env.encryptMsg(msg); String symKey = env.getSymKey(); String encKey = env.encryptKey(symKey);
AES加密明文,通过调用
getSymKey()
方法,获得AES的密钥,对于对称密钥使用椭圆曲线进行加密。数字信封解密
String decKey = env.decryptKey(encKey); env.setSymKey(decKey); String decMsg = env.decryptMsg(encMsg);
使用椭圆曲线进行解密,得到AES的对称密钥,调用
setSymKey()
方法,将AES对称密钥传入对象中,然后使用AES进行解密,得到的decMsg
即为解密后的密文。JavaScript SDK
实现JS的SDK,分别用到了aes.js、ecc-ecies.js以及secp256k1.js,其中secp256k1.js提供了椭圆曲线中
secp256k1
这条曲线的相关参数和接口。接口说明(aes.js)
生成密钥
function generateKey() function StringCodeParse()
generateKey()
会随机生成16字节(256位)的十六进制的字符串(小写字母)。StringCodeParse()
将以上生成的字符串(大写字母,对于字符串可以用toUpperCase()
函数进行转换)转换成适用于aes的密钥。加密
function encrypt(plaintext, key, iv)
加密函数,其中
plaintext
接收的是明文数据的字符串,key
是由StringCodeParse()
转换的密钥,iv
是填充向量。由于java版本和JS版本采用的是不同的填充格式,因此为保证二者互通,指定iv向量的值iv = "30313233343536373839414243444546"
。解密
function decrypt(ciphertext, key, iv)
解密函数,其中
ciphertext
是密文字符串,key
是由StringCodeParse()
转换的密钥,iv
是填充向量。由于java版本和JS版本采用的是不同的填充格式,因此为保证二者互通,指定iv向量的值iv = "30313233343536373839414243444546"
。接口说明(ecc-ecies.js)
生成公私钥对
function generateSK() function generatePK(sk)
generateSK()
生成私钥,generatePK(sk)
根据私钥生成公钥,其中sk
是由generatePK(sk)
直接生成的。function SK2Bytes(sk) function PK2Bytes(pk) function Bytes2Key(bytes)
以上三个函数中,前两个分别是将私钥、公钥转成byte数组,最后一个是将byte数组转成用私钥/公钥的格式。
加密
function encrypt(pub, msg)
加密函数,
pub
为上文生成的公钥,msg
为需要加密的明文字符串,函数的输出为Array数组。解密
function decrypt(prv, cyph)
解密函数,
prv
为上文生成的私钥,cyph
为加密的Array数组,函数输出为解密结果的字符串。function StringToHex(bytes)
将字符串形式的密文,转换成适合js解密的Array数组。
具体实例
var aes = require("./aes") var CryptoJS = require("crypto-js") var ecc = require("./ecc-ecies")
引入对应的包。
生成密钥
var key = aes.generateKey() var iv = "30313233343536373839414243444546" var key1 = aes.StringCodeParse(key.toUpperCase()) var secKey = ecc.generateSK() var pubKey = ecc.generatePK(secKey)
其中
key
是生成的256位的随机序列,key1
是转换后的AES密钥。secKey
是椭圆曲线的私钥,pubKey
是椭圆曲线的公钥。加密
var msg = "This is a plaintext message from alice to bob" var encMsg = aes.encrypt(msg, key1, iv) var encKey = ecc.encrypt(pubKey, key)
对明文
msg
使用AES进行加密,得到encMsg
,对key
使用椭圆曲线进行加密,得到encKey
。解密
var decKey = ecc.decrypt(secKey, encKey) decKey = aes.StringCodeParse(decKey) var decMsg = aes.decrypt(encMsg, decKey, iv) console.log(decMsg)
对
encKey
进行解密,得到AES对称密钥,解密的密钥使用StringCodeParse()
函数进行转换,以适应在JS的AES中进行解密。得到的decKey
,使用椭圆曲线进行解密,最后打印到控制台。
JAVA和JS互通调用实例
JAVA加密,JS解密
java部分:
envelop env = new envelop(); String msg = "hello world!"; String encMsg = env.encryptMsg(msg); String symKey = env.getSymKey(); String encKey = env.encryptKey(symKey); System.out.println("##encMsg:"); System.out.println(encMsg); System.out.println("##encKey:"); System.out.println(encKey); System.out.println("##secKey:"); System.out.println(env.getEciesSecKey());
输出结果:
##encMsg: 8cf8d9b71da857a75ee5ad683f6a2ba5 ##encKey: eaed4c48657ec38ca12eafbdf674c7653df801e8f115cd7e895f8a0a8034eb54e3b18ba30abf2bfc61c923b75288444844ec5b926e4d508760b7d2b41672525addb691cfb39c40c2e92bf0ef9c62e21acd4cd44ede52ffa425d4a4b6c96ce521996fe2d69b21537defc668269faadd981290e7d8d631d5b7ed73fb0ff436b2762e7a3976ea358b46a653a85d4f57eb44 ##secKey: 30dba07390235aa9da79fdc7778fe256c0058bc0cb12a64e943510735a2c8aaf
js解密:
var aes = require("./aes") var CryptoJS = require("crypto-js") var ecc = require("./ecc-ecies") var secp256k1 = require("./secp256k1") var iv = aes.iv var encMsg = "8cf8d9b71da857a75ee5ad683f6a2ba5" var encKey = "eaed4c48657ec38ca12eafbdf674c7653df801e8f115cd7e895f8a0a8034eb54e3b18ba30abf2bfc61c923b75288444844ec5b926e4d508760b7d2b41672525addb691cfb39c40c2e92bf0ef9c62e21acd4cd44ede52ffa425d4a4b6c96ce521996fe2d69b21537defc668269faadd981290e7d8d631d5b7ed73fb0ff436b2762e7a3976ea358b46a653a85d4f57eb44" var secKey = "30dba07390235aa9da79fdc7778fe256c0058bc0cb12a64e943510735a2c8aaf" secKey = secp256k1.uint256(secKey, 16) var encKey = ecc.StringToHex(encKey.toUpperCase()) var decKey = ecc.decrypt(secKey, encKey) decKey = aes.StringCodeParse(decKey) var decMsg = aes.decrypt(encMsg, decKey, iv) console.log(decMsg)
输出结果:
hello world!
值得注意的是,在secKey做为字符串不能直接做为私钥进行解密,需要使用
secp256k1.js
中的函数进行映射,将其映射到椭圆曲线上,以适应解密。JS加密,JAVA解密
JS加密部分:
var aes = require("./aes") var CryptoJS = require("crypto-js") var ecc = require("./ecc-ecies") var secp256k1 = require("./secp256k1") var key = aes.generateKey() var iv = aes.iv var key1 = aes.StringCodeParse(key.toUpperCase()) var secKey = ecc.generateSK() var pubKey = ecc.generatePK(secKey) var msg = "This is a plaintext message from alice to bob" var encMsg = aes.encrypt(msg, key1, iv) var encKey = ecc.encrypt(pubKey, key) console.log("sk:") console.log(secKey) console.log("encMsg:") console.log(encMsg) console.log("encKey:") console.log(secp256k1.Bytes2Hex(encKey))
加密后的输出结果
sk: <BN: 5965f0ac0cbc8c1de77d788df14586b494485816ede997ca3ac1365b52076eb9> encMsg: 45f15773c2d848e6529f7982729c01fb0c1409de130146977b393851f13afe630093e4ff0f0943ecd235edf2a2745475 encKey: 53225863dbddfb98ae018c9790e57057cddd8a1b0ecf48bf12238da1c90be013b0397cc9ea8f0a0630a8dc903a9c7d3f93e8fa8a150e720eab4cf9af00ce1d12229ba87a99f8440e22d6ed3aec85d4fa964b6b508fee05ea18f5ab152b8f6543d68b0cedbb4ad5e97ede7c38089b34ad76bb47581aa89b0e0cb261e929edba7d55dd671cad8c56004fe16068ea540aad
java解密:
public void testDecrypt() throws Exception { envelop env = new envelop(); String sk = "5965f0ac0cbc8c1de77d788df14586b494485816ede997ca3ac1365b52076eb9"; env.setEciesSecKey(sk); String encMsg = "45f15773c2d848e6529f7982729c01fb0c1409de130146977b393851f13afe630093e4ff0f0943ecd235edf2a2745475"; String encKey = "53225863dbddfb98ae018c9790e57057cddd8a1b0ecf48bf12238da1c90be013b0397cc9ea8f0a0630a8dc903a9c7d3f93e8fa8a150e720eab4cf9af00ce1d12229ba87a99f8440e22d6ed3aec85d4fa964b6b508fee05ea18f5ab152b8f6543d68b0cedbb4ad5e97ede7c38089b34ad76bb47581aa89b0e0cb261e929edba7d55dd671cad8c56004fe16068ea540aad"; String decKey = env.decryptKey(encKey); System.out.println("##decKey:"); System.out.println(decKey); env.setSymKey(decKey); String decMsg = env.decryptMsg(encMsg); System.out.println("##decMsg:"); System.out.println(decMsg); }
解密后的输出结果:
##decKey: E59C191F3EB84A6B5E7C144F3DAFEA1E ##decMsg: This is a plaintext message from alice to bob
-
@昌用 可喜可贺!