4、SM3 的消息填充与初始值

说实话,SM3 的填充规则跟 SHA-256 长得挺像。我第一次看标准文档的时候,觉得这块没啥难度——不就是补位嘛。可后来在项目中真正动手写代码,才发现越是简单的地方,越容易翻车。

你想想看,哈希算法处理消息的单位是分组,SM3 的分组大小是 512 比特。但实际的消息哪有那么巧,刚好是 512 的整数倍?所以必须填充。我今天就把填充规则和初始哈希值彻底讲透,争取让你看完就能上手写代码,不在踩坑上浪费时间。

消息填充规则

填充的目标只有一个:让消息长度变成 512 比特的整数倍。SM3 的填充分三步,顺序固定,一步都不能错。

  1. 补一个“1”:在消息末尾追加 1 比特的“1”。
  2. 补 k 个“0”:接着追加 k 个比特的“0”,k 是满足 (l + 1 + k) ≡ 448 mod 512 的最小非负整数。说白了,就是让“原始长度 + 1 + k”除以 512 余 448。
  3. 补 64 位长度:最后追加 64 比特,用大端序表示原始消息的比特长度 l。

核心公式:

(l + 1 + k) ≡ 448 (mod 512),其中 l 为原始消息比特长度,k 为补 0 的个数。

填充后总长度 = l + 1 + k + 64,一定是 512 的整数倍。

我举一个具体的例子。假设原始消息是 "abc",长度为 24 比特(3 字节)。

  • 先补 1 个“1”:长度变为 25 比特。
  • 需要补 k 个“0”,让 25 + k ≡ 448 mod 512 → k = 423。
  • 再补 64 比特的长度值:24 的 64 位大端表示为 0x0000000000000018。

填充后的消息总长度 = 24 + 1 + 423 + 64 = 512 比特,刚好一个分组。我在项目里第一次验证填充逻辑时,就是拿 "abc" 做的测试,跟标准向量对上了才放心。

SM3 消息填充与初始值流程 原始消息 (M) 填充 1 位 填充 k 个 0 填充64位长度 512位分组 迭代压缩 初始值 IV 关键说明: 填充目标:使消息长度为512bit的整数倍。k 满足:(l + 1 + k) ≡ 448 (mod 512),其中 l 为原始消息比特长度。

小技巧:我在代码里实现填充时,习惯先把原始长度 l 算出来并保存好,再做位操作。因为最后一步需要用到 l,一旦开始填充,原始长度就丢了。提前存一份,省得到时候抓瞎。

初始哈希值(IV)

SM3 的初始值 IV 是 8 个 32 比特的字,直接写死在标准里。迭代压缩的第一轮就用它们作为初始状态。说白了,这 8 个数就是哈希运算的“种子”。

变量 值(十六进制) 说明
V0 0x7380166F 第1个初始字
V1 0x4914B2B9 第2个初始字
V2 0x172442D7 第3个初始字
V3 0xDA8A0600 第4个初始字
V4 0xA96F30BC 第5个初始字
V5 0x163138AA 第6个初始字
V6 0xE38DEE4D 第7个初始字
V7 0xB0FB0E4E 第8个初始字

我个人习惯把这 8 个值记成两组:前四个是一组,后四个是一组。V0~V3 看起来比较“整”,V4~V7 更像随机数。不过这只是帮助记忆,实际用的时候直接复制粘贴就好。

填充与初始值的协同

填充完成后,消息被分割成若干个 512 比特的分组。每个分组依次进入迭代压缩函数,而初始 IV 就是压缩函数第一轮的输入状态。

你想想看,如果没有正确的填充,分组长度不对,压缩函数根本跑不起来。如果没有正确的 IV,算出来的哈希值全都不对。这两个东西就像是算法的地基,地基歪了,上面建什么都白搭。

常见陷阱

避坑指南:

  • 字节序搞反:64 位长度字段必须用大端序。我曾经因为手误写了小端序,结果算出来的哈希值跟标准向量全对不上,排查了整整一个下午。
  • k 算错:k 是满足条件的最小非负整数,不是随便选一个。我见过有人直接 k = 448 - l - 1,结果算出来负数,一脸懵。
  • 漏掉长度字段:填充的最后 64 位是原始消息长度,不是填充后的长度。这个千万别搞混。

嗯,还有一个细节:SM3 的初始 IV 是固定的,但如果你做的是 HMAC-SM3 或者定制化实现,初始值可能会被修改。不过标准 SM3 场景下,就用上面表里的 8 个数,一个字都不要改。

验证方法:我每次实现完填充逻辑,都会拿空字符串和单字符做测试。空字符串填充后应该是 512 比特,单字符填充后也是 512 比特。跟标准输出向量对一下,没问题再往下走。这样能提前过滤掉大部分低级错误。