3. SM3 密码杂凑算法原理归纳总结
SM3 是国密标准里的杂凑算法。说白了,就是把任意长度的消息,压缩成一个固定 256 位的摘要。我们平时说“哈希”,指的就是这个。
我最早接触它时,总觉得跟 SHA-256 差不多。后来做国密改造项目,才发现细节上差别不小。踩过坑,才算真正搞明白。
3.1 算法核心流程
SM3 的处理过程可以分成四步。每一步都有讲究。我们一起捋一遍。
- 消息填充:补位到 512 位的整数倍。
- 初始值设定:用 8 个 32 位寄存器装初始向量。
- 迭代压缩:对每个 512 位分组做 64 轮计算。
- 输出杂凑值:最终 8 个寄存器的值就是 256 位摘要。
你想想看,核心就在迭代压缩那一步。64 轮,每轮都用布尔函数和置换运算。强度就在这里。
3.2 消息填充细节
填充规则很简单。但我在第一次实现时还真出过 bug。
- 先在消息末尾追加一个 1 bit。
- 然后补 0,直到长度模 512 等于 448。
- 最后用 64 位表示原始消息长度。
我自己的经验:补位时最容易错的是字节序。SM3 采用大端序,跟你的平台字节序可能不一样。我曾经在 x86 机器上直接 memcpy,结果长度字段全反了。
3.3 初始值与迭代过程
SM3 的初始值 IV 是 8 个固定的 32 位常数。这里我直接写出来,方便你对照。
| 寄存器 | 初始值(十六进制) |
|---|---|
| V0 | 0x7380166F |
| V1 | 0x4914B2B9 |
| V2 | 0x172442D7 |
| V3 | 0xDA8A0600 |
| V4 | 0xA96F30BC |
| V5 | 0x163138AA |
| V6 | 0xE38DEE4D |
| V7 | 0xB0FB0E4E |
迭代时,每个 512 位分组会扩展成 132 个 32 位字(W0~W67 和 W'0~W'63)。压缩函数对这 132 个字做 64 轮运算,更新 8 个寄存器。
关键点:SM3 压缩函数里的布尔函数 FF 和 GG 会随轮数变化。前 16 轮和后 48 轮不同。我第一次写代码时忽略了这点,结果摘要全错。
3.4 压缩函数详解
压缩函数是这个算法的心脏。我习惯把它拆成三块来看。
- 消息扩展:把 16 个消息字扩成 132 个字。
- 64 轮计算:每轮用 SS1、SS2、TT1、TT2 四个中间变量。
- 状态更新:每轮结束后,8 个寄存器循环移位赋值。
下面这段代码,是我在某次项目里实际用的核心循环。注释都在。
// 64 轮压缩循环
for (j = 0; j < 64; j++) {
SS1 = rotl32(rotl32(A, 12) + E + rotl32(Tj, j), 7);
SS2 = SS1 ^ rotl32(A, 12);
TT1 = FFj(A, B, C) + D + SS2 + Wj[j];
TT2 = GGj(E, F, G) + H + SS1 + Wj_1[j];
D = C;
C = rotl32(B, 9);
B = A;
A = TT1;
H = G;
G = rotl32(F, 19);
F = E;
E = P0(TT2);
}
你仔细看看,每轮只变了 A 和 E。其他寄存器只是跟着挪。这种设计,硬件实现时很省资源。
注意:P0 置换是 SM3 独有的。它跟 SHA-256 的 Σ 函数不一样。我的一个同事直接拿 SHA-256 的代码改,结果在 P0 上翻车了。千万别偷这个懒。
3.5 SM3 与 SHA-256 对比
很多资料说 SM3 跟 SHA-256 结构相似。这个没错。但差距就在那几个布尔函数和置换上。
| 对比项 | SM3 | SHA-256 |
|---|---|---|
| 消息分组 | 512 bit | 512 bit |
| 摘要长度 | 256 bit | 256 bit |
| 轮数 | 64 | 64 |
| 扩展字数 | 132 | 64 |
| 布尔函数 | FFj / GGj(分段) | Ch / Maj(固定) |
| 置换函数 | P0 / P1 | Σ0 / Σ1 |
我个人感觉,SM3 的布尔函数分段设计,让前 16 轮和后 48 轮有不同的非线性度。这在密码分析上更难攻破。
3.6 代码实现时的避坑指南
我写 SM3 也不是一次成功的。这里面有几个坑,你提前知道,能省很多时间。
- 循环移位:SM3 里到处是循环左移。C/C++ 里标准左移不是循环的,要自己写 rotl32。
- W' 的计算:W'j = Wj ^ Wj+4,这个异或顺序不能反。我亲眼见过有人调了好几天,结果就是这里写反了。
- 多分组处理:最后一个分组填充完可能正好是 512 的整数倍,但还是要加一个完整的分组。这个规则跟 MD5 一样,别想当然地跳过。
一个小技巧:调试时可以打印每个分组的 W 数组,跟国密官方测试用例对照。前几个字对了,后面基本不会错。我自己都是这么干的。
3.7 总结
SM3 设计得很精巧。它跟 SHA-256 同属一个家族,但细节上做了强化。消息扩展用了 132 个字,布尔函数分段设计,这些都让它的安全性更高一点。
我在项目里用它做日志完整性校验。每次写入日志时计算一次 SM3 摘要,存储时把摘要附在后面。读取时重新计算对比。这样日志有没有被篡改,一眼就能看出来。
嗯,这里要注意一点。SM3 是逐分组计算的,流式处理时一定要维护好中间状态。我在做流式日志传输时,就因为这个中间状态没处理好,导致摘要不一致,排查了好久。