SM3 算法原理快速入门
做国密改造绕不开 SM3。说白了,它就是我国自主设计的杂凑函数,杂凑值长度 256 比特。你可能会问:**为什么不用现成的 SHA‑256?** 合规要求只是一方面,另一方面,SM3 在软件实现和硬件实现上都有一些独特的设计细节,实际改造时常常会遇到“看上去一样,调起来就坑”的地方。
我个人习惯先把算法结构拆开看,再对比它和 SHA‑256 到底差在哪。这一节你就跟着我走一遍 SM3 的核心理念,以后看 gmssl 源码就不会晕了。
杂凑函数结构:Merkle‑Damgård 骨架
SM3 的结构其实是经典 MD 结构的变体。整个过程可以概括为三步:
- 消息填充:在消息末尾加 1 个比特
1,再加若干个0,最后附加原始消息长度的 64 位大端表示,使总长度是 512 的整数倍。 - 分组处理:每 512 比特(64 字节)为一组,每组独立送入压缩函数。
- 迭代压缩:初始向量 IV(8 个 32 位字)与第一组压缩,输出新的中间值,再与下一组压缩,直到最后一组输出最终杂凑值。
下面的 SVG 图展示了这个流程,我建议你把它抄到笔记本上——每次做 SM3 相关开发时扫一眼就能回忆起整体路径。
与 SHA‑256 的异同
我知道很多人第一反应是:“反正都是 256 位,直接替换不就行?” 呃,不行。压缩函数细节差很多。我曾经在一个国密改造项目里试图用 SHA‑256 的硬件模块直接替换成 SM3,结果发现轮常数、布尔函数、循环移位量全部不一样,必须单独设计逻辑。下面这张表可以帮你快速对比:
| 对比项 | SM3 | SHA‑256 |
|---|---|---|
| 输出长度 | 256 比特 | 256 比特 |
| 消息块大小 | 512 比特 | 512 比特 |
| 压缩轮数 | 64 轮 | 64 轮 |
| 消息扩展方式 | W0~W15 直接取自消息块,W16~W67 通过 16 步递推生成 | W0~W15 直接取自消息块,W16~W63 通过 16 步递推生成 |
| 压缩函数特点 | 使用 3 个不同布尔函数(GG0、GG1、FF0、FF1) | 使用 2 个布尔函数(Ch、Maj) |
| 初始向量 IV | 7380166f 4914b2b9 ... |
6a09e667 bb67ae85 ... |
| 循环移位量 | 部分轮次使用 7、8、12 等 | 固定使用 2、13、22 等 |
安全强度分析
SM3 的设计目标就是 256 比特的安全等级。从抗碰撞性来看,目前公开文献中最好的攻击也只能达到大约 2248 的复杂度,远远高于 2128 的实践安全阈值。说白了,你不用担心它在碰撞攻击上出问题。
我印象比较深的是 2017 年左右,有指出对 SM3 的减轮版本(比如 16 轮)可以实现快速碰撞,但完整的 64 轮至今没有实质性突破。相比之下 SHA‑256 经历了更长时间的密码分析和实践检验,两者在安全强度上旗鼓相当。**但是请注意**,SM3 的布尔函数设计对某些类型的代数攻击有天然抵抗性,这是它的一个小优势。
典型应用场景
在实际国密改造中,你一定会遇到下面几种情况:
- SM2 数字签名中的杂凑层:SM2 签名的第一步是计算 Z 值(
ZA = SM3(ENTLA || IDA || a || b || xG || yG || xA || yA)),然后再对消息 M 做SM3(ZA || M)。如果你忘了把椭圆曲线参数和用户标识拼进去,签出来的东西根本验不过。 - 消息认证码 (MAC):直接用 SM3 拼接密钥和消息?那不安全,容易受长度扩展攻击。建议使用 HMAC‑SM3 结构,国标里有明确的标准。
- 随机数生成与密钥派生:在 KDF 里把 SM3 当单向函数用,生成会话密钥或加密密钥。我习惯配合
SM3_CTX复用上下文,避免重复初始化带来的性能开销。 - 完整性校验:文件校验、固件签名、日志验真,都可以直接用 SM3 摘要。注意和 SHA‑256 的结果长度一样,但内容不同,升级时务必更新校验逻辑。
好了,SM3 的原理和特性和你说清楚了。下一回碰到 SHA‑256 替换成 SM3 的需求,你就能从容地拆开压缩函数,看看哪一步该改、哪步可以复用。嗯,设计时多留一个心眼:填充逻辑可以共享,压缩逻辑必须独立。