第三章 SM3算法原理

SM3,说白了就是中国自己的密码学哈希标准。

它的全称是《SM3密码杂凑算法》,国标GM/T 0004-2012。在商用密码体系里,它负责干一件事:把任意长度的消息,压缩成固定256位的摘要。

我个人习惯把它看作数字指纹——消息哪怕改一个比特,指纹就完全不一样。

这一章,咱们就拆开SM3这个黑盒子,看看里面是怎么转的。同时我也会拿它和SHA-256做对比,毕竟它俩长得实在太像了。

哈希函数的通用骨架

绝大多数哈希函数都长一个模样,SM3也不例外。它们都遵循Merkle-Damgård结构。

这个结构很简单:

  • 先把消息切成等长的块(SM3是512位一块)。
  • 然后一块一块地喂给压缩函数。
  • 前一块的输出作为下一块的输入。
  • 最后一块处理完,输出的就是哈希值。

听起来就像流水线。每一块的中间结果叫做“链接变量”。一旦消息被篡改,链条就会断,摘要就会变。

关键点: SM3的输出是256位,初始值IV也是256位。它内部的工作状态是8个32位字,也就是8×32=256位。

消息填充:把数据弄整齐

SM3处理的数据是按512位分组的。但现实中的消息哪有这么巧?所以必须先做填充。

填充的规则很严格,我调过好几次因为填充顺序不对导致的bug。

填充步骤

  1. 先补一个“1”:在消息末尾追加一个比特1。对应字节就是0x80。
  2. 再补“0”:一直补0,直到满足一个条件——填充后的长度 ≡ 448 (mod 512)。
  3. 最后附上原始长度:用64位大端整数,表示原始消息的比特长度。

为什么是448?因为448 + 64(长度域) = 512,刚好凑满一块。

我曾经踩过的坑: 原始长度是指比特长度,不是字节长度。你如果传了字节长度,算出来的摘要永远不对。另外,大端字节序也别搞反了——SM3里全是Big-Endian。

举个例子

假设消息是"abc",3个字节=24比特。

  • 填充后:0x61 0x62 0x63 0x80 0x00 ... 0x00(补到448位)
  • 最后8字节:0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x18(24的十六进制)

你想想看,要是消息长度超过2^64比特怎么办?理论上不可能,实际也不会遇到。

迭代压缩:核心发动机

填充完之后,消息被分成了N个512位的消息块。接下来就是迭代压缩。

初始化

SM3有8个初始值,用16进制表示:

V0[0] = 0x7380166f
V0[1] = 0x4914b2b9
V0[2] = 0x172442d7
V0[3] = 0xda8a0600
V0[4] = 0xa96f30bc
V0[5] = 0x163138aa
V0[6] = 0xe38dee4d
V0[7] = 0xb0fb0e4e

消息扩展

每个512位的消息块(16个32位字)要先扩展成132个字:W0~W67和W'0~W'63。扩展规则是移位和异或的组合。我记得当初手写这个扩展时,算了好几遍才和标准对得上。

压缩函数

压缩函数迭代64轮。每一轮拿2个扩展后的字,加上当前的链接变量,通过一堆移位、异或、逻辑函数和加法的组合,更新这8个状态字。

具体公式我就不抄了,你查标准就行。但有一个关键:SM3用了两个特殊的布尔函数——FF和GG。它们是按轮数分段变化的:

  • 0~15轮:FF = (X ∧ Y) ∨ (¬X ∧ Z),GG = (X ∧ Y) ∨ (¬X ∧ Z)
  • 16~63轮:FF = (X ∧ Y) ∨ (X ∧ Z) ∨ (Y ∧ Z),GG = ¬(X ∧ Y) ∨ (X ∧ Z) ∨ (Y ∧ ¬Z)

说白了,就是前半段偏逻辑,后半段偏多数决。

一次迭代流程

处理第i个消息块时:

  1. 把当前链接变量V_i复制给临时变量A~H。
  2. 对A~H执行64轮压缩。
  3. 最后把A~H和V_i做异或加,得到新的V_{i+1}。

全部块处理完,V_N就是最终的哈希值,256位。

与SHA-256的异同

SM3和SHA-256都是256位输出,都基于Merkle-Damgård结构。但细节上有不少差别。我做项目时经常被问到:能不能直接用SHA-256的硬件代替SM3?答案是不行。你会碰壁的。

对比项 SM3 SHA-256
消息块大小 512位 512位
摘要长度 256位 256位
轮数 64轮 64轮
初始值 8个固定值 8个固定值(不同)
消息扩展 生成132个32位字 生成64个32位字
布尔函数 FF、GG,分两段变化 Ch、Maj,全程不变
移位运算 循环左移和右移 右移和循环右移
加法常量 每轮固定常量 64个不同常量
大端/小端 全大端 全大端
安全性 已公开,国内商用密码强认证 已公开,国际通用标准

有一个核心区别:SM3的布尔函数在16轮后变了,这意味着它的扩散速度更快。我实测过,雪崩效应比SHA-256略好一点。

注意: 虽然SM3和SHA-256结构相似,但不能混用。密钥派生、签名验签、HMAC等场景下,两者的摘要值完全不同。切换算法意味着所有已生成的数据都必须重新计算。

SM3核心流程总览

下面这张图,把SM3从输入到输出的整个流程串起来了。

你可以把它当作速查卡——每次写SM3代码前扫一眼,不容易漏步骤。

SM3 核心流程总览 原始消息(任意长度) 消息填充(补1补0+长度) 分组为512位消息块 B[0..N-1] 迭代压缩(处理每块) 初始值 IV (256位) 压缩函数 CF 消息扩展 (132字) 最终摘要(256位哈希值) 链接变量V_N = CF(V_{N-1}, B_{N-1}) 一共4大部分:消息填充 → 分组 → 迭代压缩(含消息扩展) → 输出摘要 每处理完一块,链接变量更新一次;全部块结束后,V_N即为哈希值

你看,从左到右、从上到下,逻辑很清楚。每一步都不能省,尤其填充那步最容易漏。我见过好几个同学直接拿原始消息去算,结果怎么算都不对。

SM3的安全特性

SM3被设计成具有以下性质:

  • 抗原像性:给一个摘要,找不到原始消息。
  • 抗第二原像性:给一个消息+摘要,找不到另一条消息产生相同摘要。
  • 抗碰撞性:找不到任意两条消息有相同摘要。目前SM3还没有公开的有效碰撞攻击。

从我实际对比来看,SM3在一些国产密码评测中的表现和SHA-256相当,甚至在某些场景下扩散略快。但它最硬的优势就是:符合国标,不需要额外付费,也不涉及国际专利问题。

实用建议: 如果在国内做合规项目,直接上SM3。如果做国际化产品,用SHA-256。做双算法兼容也不是不行,但要注意——SM3的摘要不能通过SHA-256重新计算得到。你的系统如果存储了SM3摘要,就不能切到SHA-256来验证。

小结

这一章,我们把SM3的骨架讲透了。消息填充、迭代压缩、和SHA-256的那些异同,你都应该清楚了。