前言
最近项目中需要对数据进行加密处理,由于项目的特殊性要求,加密统一采用国密算法进行加密。网上对国密算法的资料有限,且没有找到对应的JavaScript加密代码,无奈之下只得自己实现。加密的算法文档在国家密码管理局官网上可以查找到,此次的算法实现仅限于SM3与SM4的代码实现,SM2由于项目未做特殊要求因此还未来得及实现。
SM4密码杂凑算法简介
SM4算法是一种对称分组加密算法。该算法的分组长度为 128 比特,密钥长度为 128 比特。加密算法与密钥扩展算法都采用 32 轮非线性迭代结构。解密算法与加密算法的结构相同,只是轮询密钥的使用顺序相反,解密轮密钥是加密轮密钥的逆序。
SM4算法加密过程
根据加密密钥生成轮询密钥
根据密钥的不同,按照一定规律生成32个字(每个字四个字节)的轮询密钥,用于之后数据的加密;
将分组后的数据用轮询秘钥进行加密
加密的过程主要是用T方法和L方法,T方法转换过程中会用SBOX做一次非线性置换,经过这两个方法进行加密。
SM4算法的解密过程
根据加密密钥生成轮询密钥
根据密钥的不同,按照一定规律生成32个字(每个字四个字节)的轮询密钥,用于之后数据的加密;
将分组后的数据逆向用轮询秘钥进行加密
解密的过程主要是用T方法和L方法,T方法转换过程中会用SBOX做一次非线性置换,经过这两个方法进行加密。
SM4代码实现过程
java中的SM4实现
- 代码中encrpt() 方法是供调用的Java的方法,方法的返回值是加密完成的数组,本方法有一个传入整型数据的重载方法,表示SM4加密次数;
- generateKey()方法为未指定密钥时自动生成随机密钥的方法,不需要再使用时调用,程序会在初始化时查找密钥,若未指定自动生成密钥;
- decrpt()方法为解密方法,同样提供传入整型的重写方法,用来解密经SM4加密多次后的数据,注意使用本方法时必须指定密钥(新建SM4对象时),或者直接在加密的对象中使用。
- getKeyStr()方法用来获取加解密的密钥十六进制字符串格式
其他方法均为以上方法的子方法,由于设计较为底层的算法故不加赘述,具体请参照官方算法说明文档。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public byte[] encrpt() { this.bytes = addBytes(new byte[16 - (this.bytes.length % 16 == 0 ? 16 : this.bytes.length % 16)]); keyExtend(); int nowPo = 0; for (int i = 0; i < this.bytes.length / BLOCK; i++, nowPo += BLOCK) { int[] rows = new int[4 + 32]; for (int j = 0; j < 4; j++) { rows[j] = byteArrayToInt(this.bytes, nowPo + j * 4); } for (int k = 0; k < 32; k++) { rows[k + 4] = rows[k] ^ TMethod(rows[k + 1] ^ rows[k + 2] ^ rows[k + 3] ^ this.rks[k]); } for (int k = 0; k < 4; k++) { System.arraycopy(intToByteArray(rows[35 - k]), 0, this.bytes, nowPo + 4 * k, 4); } } System.arraycopy(this.bytes, this.bytes.length / BLOCK * BLOCK, this.bytes, this.bytes.length / BLOCK * BLOCK, this.bytes.length % BLOCK); return this.bytes; }
|
1 2 3 4 5 6 7 8 9 10 11 12
| private void keyExtend() { int[] K = new int[36]; int[] keys = { this.key[0] ^ this.FKS[0], this.key[1] ^ this.FKS[1], this.key[2] ^ this.FKS[2], this.key[3] ^ this.FKS[3] }; System.arraycopy(keys, 0, K, 0, keys.length); for (int i = 0; i < 32; i++) { this.rks[i] = K[i] ^ TPMethod(K[i + 1] ^ K[i + 2] ^ K[i + 3] ^ CK[i]); K[i + 4] = this.rks[i]; } }
|
1 2 3 4 5 6 7 8 9 10 11
| private void generateKey() { byte[] var2 = new byte[32]; this.random.nextBytes(var2); for (int i = 0; i < KEYLENGTH; i++) { this.key[i] = byteArrayToInt(var2, 4 * i); } isKeyInit = true; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public byte[] encrpt() { this.bytes = addBytes(new byte[16 - (this.bytes.length % 16 == 0 ? 16 : this.bytes.length % 16)]); keyExtend(); int nowPo = 0; for (int i = 0; i < this.bytes.length / BLOCK; i++, nowPo += BLOCK) { int[] rows = new int[4 + 32]; for (int j = 0; j < 4; j++) { rows[j] = byteArrayToInt(this.bytes, nowPo + j * 4); } for (int k = 0; k < 32; k++) { rows[k + 4] = rows[k] ^ TMethod(rows[k + 1] ^ rows[k + 2] ^ rows[k + 3] ^ this.rks[k]); } for (int k = 0; k < 4; k++) { System.arraycopy(intToByteArray(rows[35 - k]), 0, this.bytes, nowPo + 4 * k, 4); } } System.arraycopy(this.bytes, this.bytes.length / BLOCK * BLOCK, this.bytes, this.bytes.length / BLOCK * BLOCK, this.bytes.length % BLOCK); return this.bytes; }
|
1 2 3 4 5 6 7 8 9 10 11
| private void generateKey() { byte[] var2 = new byte[32]; this.random.nextBytes(var2); for (int i = 0; i < KEYLENGTH; i++) { this.key[i] = byteArrayToInt(var2, 4 * i); } isKeyInit = true; }
|
JavaScript中的SM4实现
由于Java代码的完成时间较早,JavaScript的实现代码是从Java代码移植过来的,方法命名大同小异,因此不加赘述。代码版面较长,本文就不贴了,请移步github查看。
SM3加密的使用方法
SM4加密的Java使用
最近事务繁多无法抽身用设计模式对本算法进行优化,为了稳定运行只能牺牲性能采用实例化后调用的方式进行。由于底层计算都是位运算实际使用情况尚可,以下是在Java中的使用方式:
1 2 3 4 5 6
| public static void main(String args[]){ SM4Encrp sm4 = new SM4Encrp(new byte[]{0x43,0x35}); byte bt = sm4.encrpt(); SM4Encrp sm4de = new SM4Encrp(bt,sm4.getKeyStr); byte btSource = sm4de.decrpt(); }
|
SM4加密的JavaScript使用
由于前台多是处理字符串和二进制数据,故统一采用传递二进制的方式进行杂凑计算。如果有求字符串杂凑值的必要可以调用以下方法转换为二进制byte数组进行使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| function stringToByte(str) { var bytes = new Array(); var len, c; len = str.length; for (var i = 0; i < len; i++) { c = str.charCodeAt(i); if (c >= 0x010000 && c <= 0x10FFFF) { bytes.push(((c >> 18) & 0x07) | 0xF0); bytes.push(((c >> 12) & 0x3F) | 0x80); bytes.push(((c >> 6) & 0x3F) | 0x80); bytes.push((c & 0x3F) | 0x80); } else if (c >= 0x000800 && c <= 0x00FFFF) { bytes.push(((c >> 12) & 0x0F) | 0xE0); bytes.push(((c >> 6) & 0x3F) | 0x80); bytes.push((c & 0x3F) | 0x80); } else if (c >= 0x000080 && c <= 0x0007FF) { bytes.push(((c >> 6) & 0x1F) | 0xC0); bytes.push((c & 0x3F) | 0x80); } else { bytes.push(c & 0xFF); } } return bytes; }
|
使用方式如下所示:
1 2 3 4 5 6 7 8
| var test = new SM4Encrpt({ (key/keyStr):***, bytes/enStr:*** }); test.encrpt(); test.encrpt(100); test.decrpt(); test.decrpt(100);
|
小结
热衷造轮子,
方便造车人。
愿别人的收获中也有属于自己的耕耘。
最后更新时间: