SM3加密的实现

前言

最近项目中需要对数据进行加密处理,由于项目的特殊性要求,加密统一采用国密算法进行加密。网上对国密算法的资料有限,且没有找到对应的JavaScript加密代码,无奈之下只得自己实现。加密的算法文档在国家密码管理局官网上可以查找到,此次的算法实现仅限于SM3与SM4的代码实现,SM2由于项目未做特殊要求因此还未来得及实现。

SM3密码杂凑算法简介

SM3算法是一种验证签名类算法,类似于MD5SHA1等算法,适用于商用密码应用中的数字签名和验证、消息认证码的生成与验证以及随机数的生成, 可满足多种密码应用的安全需求。

SM3杂凑过程

1.数据填充
将原数据二进制进行填充,填充后的消息m′ 的比特长度为512的倍数。
2.消息扩展
将填充后的消息m′按512比特进行分组,将每组消息分组B(i)按一定方法扩展生成132个字,每个字为四个字节。
3.迭代压缩
按照一定规律进行64次迭代压缩,压缩后的值存入8个字节寄存器里面。
4得到杂凑值
最终计算出256位杂凑值

SM3代码实现过程

java中的SM3实现

代码中 getEncrpResult() 方法是获取杂凑值的方法,方法的返回值是一个整型数组,大部分的杂凑值为了便于查看与比较,通常采用十六进制的字符串进行表示,本类也同样提供实现方法,实现方法是 getStringEncrpResult() 。其他方法均为以上方法的子方法,由于设计较为底层的算法故不加赘述,具体请参照官方算法说明文档。

1
2
3
4
5
6
7
public int[] getEncrpResult()
{
fillBinDatas();// 填充
ExtendedPacket();// 扩展
IterationMethod();// 迭代压缩
return result;
}
1
2
3
4
5
6
7
8
9
10
public String getStringEncrpResult()
{
int[] rs = getEncrpResult();
String string = "";
for (int i = 0; i < rs.length; i++)
{
string += Integer.toHexString(rs[i]) + " ";
}
return string;
}

JavaScript中的SM3实现

由于Java代码的完成时间较早,JavaScript的实现代码是从Java代码移植过来的,方法命名大同小异,因此不加赘述。加密方法为 getEncrpResult()getStringEncrpResult()

1
2
3
4
5
6
7
8
me.getEncrpResult = function()
{
me.init();
fillBinDatas();// 填充
ExtendedPacket();// 扩展
IterationMethod();// 迭代压缩
return me.result;
}
1
2
3
4
5
6
7
8
9
10
11
12
me.getStringEncrpResult = function()
{
var rs = me.getEncrpResult();
var string = "";
var res = new Uint32Array(me.result.length);
copyArray(rs, 0, res, 0, rs.length);
for (var i = 0; i < rs.length; i++)
{
string += res[i].toString(16) + " ";
}
return string;
}

SM3加密的使用方法

SM3加密的Java使用

本人水平有限无法熟练处理线程安全问题,为了稳定运行只能牺牲性能采用实例化后调用的方式进行。由于底层计算都是位运算实际使用情况尚可,以下是在Java中的使用方式:

1
2
SM3Encrp sm3 = new SM3Encrp(new byte[] { 0x61, 0x62, 0x63, (byte) 0xff });
String sm3Code = s.getStringEncrpResult();

除了以上构造方法,还提供输入流和字符串的构造方法重载,基本上满足了项目中的使用。

SM3加密的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
/**
* 字符串转byte数组
*
* @param {*字符串}
* str
*/
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 sm = new SM3Entrpt([0x61, 0x62, 0x63, 0x64, 0x61, 0x62, 0x63, 0x64, 0x61,
0x62, 0x63, 0x64, 0x61, 0x62, 0x63, 0x64,
0x61, 0x62, 0x63, 0x64, 0x61, 0x62, 0x63, 0x64, 0x61, 0x62, 0x63, 0x64, 0x61,
0x62, 0x63, 0x64, 0x61, 0x62, 0x63, 0x64,
0x61, 0x62, 0x63, 0x64, 0x61, 0x62, 0x63, 0x64, 0x61, 0x62, 0x63, 0x64, 0x61,
0x62, 0x63, 0x64, 0x61, 0x62, 0x63, 0x64,
0x61, 0x62, 0x63, 0x64, 0x61, 0x62, 0x63, 0x64]);
var result = sm.getStringEncrpResult();

如果要求字符串的杂凑值,使用方法如下:

1
2
3
var str = "测试代码";
var sm = new SM3Entrpt(stringToByte(str));
var result = sm.getStringEncrpResult();

小结

九层之台起于累土,
千里之行始于足下。

愿别人的收获中也有属于自己的耕耘。