4. SM3算法原理:消息填充、迭代压缩、输出长度
说到哈希算法,SM3 是国密体系的基石之一。简单理解,哈希算法就像把一段数据加工成一个独一无二的数字指纹。SM3 更是金融行业最重要的哈希标准,像签名验签、完整性校验、密码保护,都离不开它。
我刚开始搞这块时,觉得 SM3 不就是个哈希函数吗?不就是算个摘要吗?后来真到项目中,发现这里面的坑可不少。从消息填充、迭代压缩到最后输出,每一步都有讲究。你想想看,如果摘要算法出问题,整个签名体系就全面崩盘。所以今天我带你把它彻底弄明白。
- 输出长度:256 比特(32 字节)
- 消息分组:512 比特(64 字节)
- 字长:32 比特
- 压缩函数:64 轮迭代
- 安全强度:与 SHA-256 相当
4.1 消息填充
SM3 处理消息的单位是 512 比特的分组。如果消息长度不是 512 的倍数,就需要填充。填充过程分两步,先补位,后补长度,说白了就是把原始消息拉长到合适的尺寸。
- 第一步: 在消息末尾追加比特 "1",然后追加 k 个比特 "0",使得填充后的长度满足 (len + 1 + k) ≡ 448 mod 512。嗯,这里的 448 是固定的,也就是填充完后要留出 64 比特放长度信息。
- 第二步: 把原始消息的长度(以比特为单位)用 64 比特无符号整数表示,高位在前。拼接到第一步的结果后面。最后的总长度就是 512 的整数倍。
我刚开始看规范时,觉得填充这部分太简单了。后来真动手写代码,才发现容易栽在两个地方:一个是长度编码时的字节序问题,另一个是填充后分组边界判定。我曾经在适配老版本国密设备时,因为双方填充逻辑的位数计数方式不同,导致校验失败。排查了好几天,最后发现是填充时把消息长度多算了 8 个比特。
4.2 迭代压缩
这是 SM3 最核心的部分。填充完的消息被切分成 512 比特分组。每个分组经历 64 轮迭代压缩。我个人习惯把这个过程看作是“数据搅拌机”——输入的中间状态和消息分组反复混合,产生新的中间状态。
具体流程是这样:
- 初始化:设置 8 个 32 比特字作为初始值 IV(V0)。规范里写得很清楚,这个 IV 是固定的。
- 消息扩展:每个 512 比特分组扩展成 132 个 32 比特字。前 16 个字直接取自分组,后面 116 个字通过线性变换生成。
- 压缩函数:扩展出的字与当前状态(Vj)进行 64 轮运算。每轮会用到布尔函数 FF、GG 以及循环移位。最后生成新的状态 Vj+1。
- 迭代:对所有分组重复步骤 2-3,直到处理完所有分组。最终状态 Vn 就是 256 比特的消息摘要。
压缩函数里,我特别想提一个点:布尔函数 FF 和 GG 的位置。你在看规范时,会发现 FF 和 GG 的定义里既有选择函数也有多数函数。它们在不同的轮中使用不同的常数。关于这些布尔函数的实现,我个人习惯用位运算直接写,比查表快得多。
我曾经在调试 SM3 性能时,发现每次迭代都要做很多循环移位和异或运算。我当时尝试把扩展后的 132 个字缓存在局部变量中,而不是每次都重新计算。结果性能提升了将近 40%。所以对于高并发场景,内存换时间是值得的。
4.3 输出长度
SM3 的输出是固定的 256 比特,也就是 32 字节。这个长度有什么讲究?
- 安全强度:256 比特输出意味着抗碰撞安全强度为 2^128,抗原像安全强度为 2^256。这在金融行业里已经绰绰有余了。
- 适用场景:数字签名、完整性校验、密钥派生、随机数生成。说白了,大部分需要哈希的场景 SM3 都能胜任。
- 兼容性:很多现有系统(比如支付宝、微信的开放平台)都支持 256 比特摘要,所以 SM3 的 32 字节输出可以直接嵌入现有报文结构。
如果我有一个消息要哈希,SM3 给我的是一个 32 字节的二进制摘要。很多开发者在展示摘要时会转成十六进制字符串,也就是 64 个十六进制字符。和 SM2 签名配合使用时,这个 32 字节的统一长度让报文字段设计变得很简洁。
4.4 与 SHA-256 的对比
很多同学问我:SM3 和 SHA-256 有什么区别?哪个更好?我个人认为,两者在安全强度上基本相当,只是设计细节不同。我做一个对比表。
| 项目 | SM3 | SHA-256 |
|---|---|---|
| 输出长度 | 256 比特 | 256 比特 |
| 消息分组 | 512 比特 | 512 比特 |
| 压缩轮数 | 64 轮 | 64 轮 |
| 布尔函数 | FF + GG(含选择/多数) | Ch + Maj |
| 字长与移位 | 32 位,循环左移 | 32 位,右移 + 循环右移 |
| 安全强度 | 2^128(抗碰撞) | 2^128(抗碰撞) |
| 国际认可 | 国密标准,部分 ISO | NIST,全球通用 |
从表中能看出来,两者非常接近。SM3 的布尔函数设计上比 SHA-256 更复杂一些,但换来的不是本质上的安全提升,而是与国密其他算法(如 SM2、SM4)的配套使用更顺畅。我记得在迁移 SHA-256 到 SM3 时,主要是替换算法调用,报文结构基本不用动。
不过要留意一个区别:SM3 在扩展阶段的字生成方式和 SHA-256 不同。SM3 用到的是异或和循环移位的组合,而 SHA-256 用的是右移和异或的组合。这个差异导致 SM3 的扩散性略好,但对开发者来说感知不强。
下面我把整个 SM3 运算流程画了出来。从输入消息开始,经过填充、分组、迭代压缩,最终输出 256 比特摘要。这能帮你快速形成整体认知。
说实话,SM3 的原理并不复杂。但真正要把它用好,需要熟悉填充边界、压缩函数的实现细节以及字节序的处理。我建议你写一个测试用例,拿一个固定消息(比如 "abc")手工计算一遍摘要,然后和标准实现对比。这样做一次,你就能彻底理解 SM3 的全貌。