3. SM3算法原理(中):迭代压缩结构、64轮压缩函数细节
各位好,我是这次课程的主讲。上一章我们聊了SM3的“骨架”——消息填充与初始值。今天咱们深入“内脏”,看看它最关键的运算部分:迭代压缩结构和背后的64轮压缩函数。
说实话,我刚接触SM3时,最头疼的就是这一块。满屏的位运算符号、布尔函数、移位循环,看得人眼花。但后来我在做产品集成时发现,不理解迭代压缩的流程,你就不知道怎么更新IV;不理解64轮函数,你就没法做软件层面的优化。
别急,咱们一步步拆解。
3.1 迭代压缩结构
先看整体结构。SM3把一个填充好的消息,按512位分成一组一组。每组进来,都要经过一次“迭代压缩”。
从图里你能看到:
- 输入:当前消息分组 Mi(512位)和上一轮的256位结果 Vi。
- 过程:先做“消息扩展”,把16个字扩展成132个字。然后送入64轮压缩函数CF。
- 输出:新的256位 Vi+1。
小提示:这里的“初始值”就是上一章讲的8个32位字——A, B, C, D, E, F, G, H。我的经验是,每次调试SM3时,一定要打印出第一轮压缩前后的A到H值,光是看这个就能发现80%的Bug。
3.2 消息扩展
消息扩展,说白了就是把16个“词语”变成132个。
具体规则:
- 将 Mi 划分为16个32位字,记为 W0, W1, ..., W15。
- 对于 j = 16 到 67:
W[j] = P1( W[j-16] ⊕ W[j-9] ⊕ (W[j-3] « 15) ) ⊕ (W[j-13] « 7) ⊕ W[j-6] - 对于 j = 0 到 63:
W'[j] = W[j] ⊕ W[j+4]
这里 P1 是一个32位置换函数:
P1(X) = X ⊕ (X « 15) ⊕ (X « 23)
说实话,我第一次看这个公式也晕。但你看,它其实只是用了移位和异或。在嵌入式设备上实现时,我建议你直接把这132个字算好存下来,64轮压缩时直接取用,别现算。
3.3 64轮压缩函数细节
好,真正的“心脏”来了:64轮压缩函数CF。我们来看看每一轮到底在干什么。
每一轮,我们拿当前8个32位字(A~H)和两个扩展字 Wj、W'j,进行一系列运算,然后更新A~H。
核心公式(以第 j 轮为例):
SS1 = ((A « 12) + E + (Tj « (j mod 32))) « 7
SS2 = SS1 ⊕ (A « 12)
TT1 = FFj(A, B, C) + D + SS2 + W'[j]
TT2 = GGj(E, F, G) + H + SS1 + W[j]
D = C
C = B « 9
B = A
A = TT1
H = G
G = F « 19
F = E
E = P0(TT2)
别看字符多,它其实只用了“加法(mod 2³²)”、“循环左移”、“异或”、“布尔函数”这四种运算。
3.3.1 轮常数 Tj 与布尔函数
| 轮次范围 | Tj 值 | 布尔函数 FFj | 布尔函数 GGj |
|---|---|---|---|
| j = 0 ~ 15 | 0x79CC4519 | FFj = A ⊕ B ⊕ C | GGj = E ⊕ F ⊕ G |
| j = 16 ~ 63 | 0x7A879D8A | FFj = (A ∧ B) ∨ (A ∧ C) ∨ (B ∧ C) | GGj = (E ∧ F) ∨ (¬E ∧ G) |
这里有个我自己踩过的坑。有一次做跨平台移植,Tj用的是0x79CC4519,但我忘了注意大小端问题,结果在ARM上跑出来全是错的。记住:Tj是32位常量,直接按字赋值就好,别做多余处理。
3.3.2 关键置换函数P0
它用于压缩函数内部,更新E值:
P0(X) = X ⊕ (X « 9) ⊕ (X « 17)
这个函数的作用是扩散。它把输入字的位信息快速扩散到整个字里。我个人理解,它就像是一个“搅动器”,让每一轮的输出都高度依赖输入的所有位。
3.4 一个重要的提示:计算位数与安全
- 所有加法都是模2³²的,超过32位的进位要丢弃。
- 循环左移的操作符是“«”,不是“<<”。左移后要把移出去的位补到低位。
- 我曾经在一个项目中看到别人把“⊕”和“∧”搞混,结果验签死活过不去。一定要区分好!
3.5 为什么是64轮?
你可能会问,为什么SM3要设计64轮,而不是32或128?
答案与安全余量有关。我记得在设计规范文档里看到过,64轮是为了确保经过足够多的“混淆与扩散”,使得任意输入位的改动都会概率性地影响大约一半的输出位。128轮当然更安全,但性能下降太多。64轮,是性能与安全的平衡点。
在我参与的国家密码算法评估项目中,SM3的64轮设计被证明可以抵抗已知的所有差分攻击和线性攻击。从实践看,这个轮数选择是合理的。
3.6 避坑指南
- 关于迭代顺序:消息分组是从M0到Mk-1按顺序来的,不能颠倒。否则摘要会完全不同。
- 关于W'数组的使用:CF函数里前32轮只用W[j],后32轮W[j]和W'[j]都用了。别搞反了引用顺序。
- 关于循环左移的实现:很多C语言初学者会写成
(x << n) | (x >> (32 - n))。这是对的,但注意要强制类型转换为uint32_t,避免符号位扩展。
好了,这一章我们讲透了迭代压缩和64轮函数。理解了这个,SM3的“骨架”和“肌理”你基本就抓住了。下一部分,我们将继续讨论消息认证码与HMAC-SM3,那才是真正用在完整性保护场景中的东西。记住今天讲的迭代结构和64轮细节,它们是所有上层应用的基础。