SM3的填充规则:比特填充、长度附加、分块处理
聊到SM3的填充,我得先跟你说句实话——填充这一步看着简单,但坑不少。我在几个商用密码项目中都遇到过因为填充写错导致整个摘要对不上的情况。调了三天才发现是长度附加的比特序搞反了……嗯,从那以后我每次写填充代码都会反复核对标准。
SM3的输入是任意长的比特串,但它的压缩函数一次只能处理512位(64字节)。所以你得先把消息切成一块一块的。怎么切?标准规定了三步:比特填充 → 长度附加 → 分块。咱们一个一个拆开看。
第一步:比特填充
说白了,就是在消息尾巴上塞一些比特,让剩余空间刚好能放下64位的长度值。填什么呢?标准规定:
- 先补一个“1”比特。
- 然后补“0”比特,直到满足条件。
条件是什么?假设原始消息长度是 l 比特。填充后总长对512取模应该等于448。也就是:(l + 1 + k) mod 512 = 448,其中k是补0的个数。k最小是0,最大是511。
为什么会是448?因为后面还要附加64位的长度,448+64=512,正好一块。我习惯先算一下:
原始消息长度 l 比特
先加1个"1" → l+1
然后找最小的k,使 (l+1+k) % 512 == 448
补k个"0"
第二步:长度附加
填充完成后,消息长度变成了512的整数倍减64。现在在末尾附上原始消息的长度信息。SM3用的是64位无符号整数,大端序(Big-Endian)表示。
什么是大端序?就是高位字节放在低地址。举个例子:如果原始长度是0x1234,那在64位里就是0x0000000000001234。字节顺序从左到右,高字节在前。这个和SM3内部用的字节序一致。
注意:长度单位是比特,不是字节。原始消息可能有几GB甚至更大,但SM3最大能处理2^64 - 1比特,约2EB,实际够用。
| 组成部分 | 长度(比特) | 内容 |
|---|---|---|
| 原始消息 | l | 任意 |
| 填充位 | 1 + k | 1个“1” + k个“0” |
| 长度表示 | 64 | l的大端序64位整数 |
填充后的总长度 = l + 1 + k + 64,一定是512的整数倍。每512位就是一个分组。
第三步:分块处理
填充后的消息长度是512的整数倍,直接按512位(64字节)切块就行了。每个块记作 Bi,i从0到n-1。SM3的压缩函数就是对这些块依次处理,每次更新256位的内部状态。
你想想看,如果原始消息刚好是512位的整数倍,那还需要填充吗?标准规定必须填充,即使长度已经满足模512=0。这时候需要补一个完整的512位块:先补“1”,再补447个“0”,再加64位长度。这条我曾在代码评审时指出来过,有人直接跳过填充导致摘要全错。
填充规则流程图
下面这张图把整个填充到分块的流程串起来了,你可以对照着看。
完整的填充示例
咱们跑一个具体的例子。假设原始消息只有1个字节:0x61(字母'a')。长度l=8比特。
- 比特填充:补“1” → 消息变9比特。448 - 9 = 439,所以补439个“0”。此时总长8+1+439=448比特。
- 长度附加:原始长度8(0x00000000000008)以64位大端序附加。这64比特放在最后。
- 总长度:448 + 64 = 512比特,正好一个分组。
你看,如果原始消息是空的(l=0),那填充后就是:先补1,再补447个0,最后加64位0。总共512位。我最初实现的时候没考虑空消息的情况,结果测试向量死活过不了……后来才补上。
常见误区汇总
我在代码审查时经常看到这几个问题,你留意一下:
| 误区 | 错误表现 | 正确做法 |
|---|---|---|
| 忽略必须填充 | 消息长度正好512整数倍时不填充 | 必须始终填充,至少增加一个块 |
| 长度单位用错 | 附加了字节数(8倍) | 必须使用比特数作为长度 |
| 字节序错误 | 用Little-Endian附加长度 | 必须用Big-Endian(大端序) |
| 填充位计数偏差 | 少补或多补了0的个数 | 严格按照 (l+1+k) % 512 == 448 |
嗯,到这里填充规则就讲完了。说白了就是三步走:先塞1和0,再挂长度,最后切块。每一步都有细节,但只要你按标准来,一次就能过。我记得最初学SM3时,自己手算了一个小消息的填充结果,然后拿公开的测试向量对了一遍,从那之后再没写错过。