4. SM4 密钥生成原理
SM4 的密钥扩展算法,说白了就是把一个 128 位的主密钥“摊开”成 32 个轮密钥。每个轮密钥用在加密或解密的一轮中。我刚接触 SM4 时总以为密钥扩展很简单,直到在项目中调试 FPGA 实现才发现,里面那个线性变换和 SM4 加密用的并不完全一样。嗯,这里我们先捋清楚原理。
4.1 密钥扩展的输入与输出
- 输入:128 位主密钥
MK,拆成 4 个 32 位字:MK0, MK1, MK2, MK3。 - 输出:32 个轮密钥
rk0, rk1, ..., rk31,每个 32 位。
你想想看,加密和解密都需要这 32 个轮密钥。解密时的轮密钥顺序是反过来的,但生成的算法完全一样。
4.2 系统参数与固定参数
密钥扩展用到了两组常数:
| 参数 | 值(十六进制) | 备注 |
|---|---|---|
| FK0 | A3B1BAC6 | 系统参数,用于异或初始轮密钥状态 |
| FK1 | 56AA3350 | |
| FK2 | 677D9197 | |
| FK3 | B27022DC |
另外还有 32 个固定参数 CK0 到 CK31,每个由特定公式生成(具体值在国密标准里,我这里不列全表,但你实现时可以直接用标准查表)。
我个人的习惯:实现时把 FK 和 CK 都写成常量数组,不要运行时计算。查表快,也不容易写错。
4.3 密钥扩展的计算步骤
- 初始异或:
K0 = MK0 XOR FK0
K1 = MK1 XOR FK1
K2 = MK2 XOR FK2
K3 = MK3 XOR FK3 - 迭代生成 32 轮(i = 0 到 31):
K_{i+4} = K_i XOR T'( K_{i+1} XOR K_{i+2} XOR K_{i+3} XOR CK_i ) - 轮密钥赋值:
rk_i = K_{i+4}
注意,这里的 T'() 变换和加密中的 T() 很像,但 线性变换 L' 不一样。T' 的构成是:
- 先经过 4 个并行 S 盒(和加密的 S 盒完全一样);
- 然后做线性变换
L':
B XOR (B <<< 13) XOR (B <<< 23)。
而加密里的 L 是左移 2、10、18、24 位。这个区别我之前在仿真时掉进过坑里——用了加密的 L 去做密钥扩展,结果加密结果全错。
4.4 轮密钥生成示例(示意值)
假设主密钥全零:
MK = 0x00000000 00000000 00000000 00000000
K0 = 0xA3B1BAC6 XOR 0x00000000 = 0xA3B1BAC6
K1 = 0x56AA3350
K2 = 0x677D9197
K3 = 0xB27022DC
第一轮(i=0):
temp = K1 XOR K2 XOR K3 XOR CK0
...(经过 S 盒和 L')
rk0 = K0 XOR T'(temp)
具体数值你可以用 SM4 官方测试向量验证。我在项目中常用下面这个验证用数据:
| 轮数 i | rk_i(十六进制) |
|---|---|
| 0 | F12186F9 |
| 1 | 41662B61 |
| 2 | 5A6AB19A |
| ... | ... |
(完整向量请参考《SM4 分组密码算法》标准附录。)
4.5 密钥扩展的特点与安全性
- 每轮引入非线性:S 盒的混淆作用让轮密钥无法从主密钥直接线性推出。
- 轮间扩散:因为
K_i会参与后续多轮的计算,即使主密钥只改一个比特,所有轮密钥都会“面目全非”。 - 正向可计算,逆向不可推算:从轮密钥反推主密钥的复杂度约等于穷举 2128。
我曾经踩过的坑:在硬件实现中,S 盒的查找表是用 ROM 还是 LUT 要小心。有一次我为了省资源,把 S 盒按字节存储,但读取顺序没对齐,结果密钥扩展每轮都错了一个字节。查了两天,最后发现是端序问题。
避坑指南:实现后先用标准测试向量做单元测试,不要等整个加解密做完再去排查。
避坑指南:实现后先用标准测试向量做单元测试,不要等整个加解密做完再去排查。
4.6 密钥扩展流程总览
我用一张 SVG 图把整个流程画出来,方便你对照理解。
流程可以总结为:主密钥 ➔ 异或系统参数 ➔ 用迭代和 T' 变换生成 32 轮密钥。解密时同样用这个流程生成轮密钥,只是使用顺序相反(rk31,rk30,...,rk0)。
4.7 小结
这一章我们重点拆解了 SM4 密钥扩展的三个要点:初始异或、迭代公式、T' 变换的 L' 与加密 L 的区别。记住这些,你在实现或验证 SM4 时就能避免常见的错误。我最早在项目中写 SM4 软件实现时,就是先把密钥扩展的单元测试写清楚,再写加解密部分——这样定位问题快很多。