SM3算法的数学基础:布尔函数、置换与模运算
聊SM3之前,我建议大家先别急着看代码。先把数学底子打扎实了,后面看算法流程就像看地图一样清楚。说白了,SM3的核心就是三种数学工具的组合拳:布尔函数、置换操作和模运算。你想想看,如果没有这些基础,直接去看那64轮压缩函数,很容易懵。
我在项目中遇到过一个兄弟,直接拿SM3的参考实现改吧改吧就用。结果测向量的时候死活不对。查了两天,最后发现是布尔函数里一个位运算的优先级搞错了。所以,嗯,我们先从最底层的砖块开始垒。
布尔函数:SM3的非线性核心
SM3用了两个布尔函数——FF_j和GG_j。它们负责把三个32位的字揉在一起,输出一个新的32位字。
这两个函数分阶段表现不同。我把它们的真值表逻辑整理了一下:
| 轮次范围 | 函数 | 逻辑表达式 | 别名 |
|---|---|---|---|
| 0 ≤ j ≤ 15 | FF_j(X,Y,Z) | X ⊕ Y ⊕ Z | 异或函数 |
| 16 ≤ j ≤ 63 | FF_j(X,Y,Z) | (X ∧ Y) ∨ (X ∧ Z) ∨ (Y ∧ Z) | 多数函数 |
| 0 ≤ j ≤ 15 | GG_j(X,Y,Z) | X ⊕ Y ⊕ Z | 异或函数 |
| 16 ≤ j ≤ 63 | GG_j(X,Y,Z) | (X ∧ Y) ∨ (¬X ∧ Z) | 选择函数 |
注意看,在前16轮里,FF和GG其实是一样的,都是简单的异或。但到了后48轮,它们就开始分岔了。我个人的理解是——前16轮做快速扩散,后48轮做非线性混淆。这个设计思路在SHA-2里也能看到影子,但SM3的布尔函数选得更讲究。
核心要点:多数函数 (X∧Y)∨(X∧Z)∨(Y∧Z) 的输出始终与多数输入一致,具备良好的雪崩效应。选择函数 (X∧Y)∨(¬X∧Z) 则是根据X的位值,在Y和Z之间做选择。这两个函数都是非线性的,这是SM3抗密码分析的关键。
置换函数:消息扩散的关键
光有布尔函数还不够。SM3还设计了两个置换函数——P_0和P_1。它们的作用就是把输入字的位顺序打乱重排。说白了,就是让1个比特的变化能影响到更多的比特。
P_0用在消息扩展部分:
P_0(X) = X ⊕ (X <<< 9) ⊕ (X <<< 17)
P_1用在压缩函数部分:
P_1(X) = X ⊕ (X <<< 15) ⊕ (X <<< 23)
这两个函数都用了3次异或和两次循环移位。你可能会问,为什么选9和17、15和23这些数字?我个人觉得,因为它们在32位字长内互为补数——9+23=32,17+15=32。这样设计可以保证每个比特经过扩散后,在输出中覆盖尽可能多的位置。
我的一点经验:在硬件实现里,循环移位是免费的——只是重新连线,不需要任何逻辑门。所以SM3选这两个置换函数,对硬件实现非常友好。如果你在做FPGA或ASIC实现,这一点会很爽。
模运算:32位字中的加法
SM3里的加法都是模232的。什么意思?就是32位无符号整数加法,溢出直接丢掉。这在密码算法里太常见了——SHA-256也用同样的套路。
我拿个实际场景举例。假设两个32位字相加:
A = 0xFFFFFFFF
B = 0x00000001
A + B = 0x00000000 (丢掉进位)
这个溢出特性在SM3的压缩函数里被用得淋漓尽致。每一轮迭代都有两次模加操作:
SS1 = ((A <<< 12) + E + (T_j <<< j)) <<< 7SS2 = SS1 ⊕ (A <<< 12)
这里的+就是模232加法。T_j是常量,前16轮用0x79CC4519,后48轮用0x7A879D8A。我个人习惯是记下这两个数字的十六进制形式,调试向量的时候经常要用到。
避坑指南:我曾经因为模加法的溢出处理翻过车。在C语言里,unsigned int的溢出是定义良好的——高位截断。但在Python或者大整数环境下,你必须手动做& 0xFFFFFFFF模操作。忘了这一步,结果全错。
三者如何协同工作
理解了这三个基础工具,SM3的完整迭代图景就清晰了。我画了一张结构图,展示消息如何通过这三层加工变成杂凑值:
从这张图能看得很清楚:消息进来先走消息扩展——这里用到P_1置换;然后进入64轮迭代——每轮里FF/GG做布尔运算,P_0做置换,模加做累加;最后出256位杂凑值。
总结一下这三者的关系
我在项目里喜欢用一个比喻:
- 布尔函数是「厨师」——决定食材怎么搭配
- 置换函数是「搅拌器」——把味道均匀扩散
- 模运算是「高压锅」——强制在32位空间里反应
缺了哪一个,SM3的安全性都会大打折扣。我建议你把这三个基础吃透了再去啃后续的压缩函数和消息扩展——它们本质上就是这三个工具的不同组合调用。
一个实操小技巧:写代码的时候,把布尔函数、置换函数、模加操作分别封装成独立的函数或宏。这样不仅好调试,将来做优化(比如查表法、位切片法)也方便。我在做SM3性能优化时就靠这个重构省了大量时间。