SM4加密流程:32轮迭代、τ变换与L变换

SM4的加密过程,说白了就是一个32轮的Feistel结构。我刚开始接触时也觉得轮数有点多,但后来才明白,正是这种轮叠轮的设计,才让算法既安全又高效。

1. 整体加密流程概览

先看一轮的处理逻辑。

输入是四个32位字:X_i, X_{i+1}, X_{i+2}, X_{i+3}。输出一个32位字 X_{i+4}。

计算公式非常简洁:

X_{i+4} = X_i ⊕ T( X_{i+1} ⊕ X_{i+2} ⊕ X_{i+3} ⊕ rk_i )

其中 T 是混合变换,由 τ 和 L 两级构成。

我个人习惯把这一轮看成“加、代换、扩散、再异或”四步,这样好记。

一轮迭代核心:
① 异或三个字和轮密钥 → ② τ非线性查表 → ③ L线性移位异或 → ④ 与最左字异或

SM4 一轮迭代结构 Xi Xi+1 Xi+2 Xi+3 ⊕ (Xi+1⊕Xi+2⊕Xi+3⊕rki) rki τ 变换 4个并行S盒 L 变换 循环左移2,10,18,24 ⊕ Xi → Xi+4 Xi+4 图例 异或/合并 非线性τ 线性L

2. 32轮迭代细节

32轮,每轮输出一个32位字 X_{i+4}。初始输入 X_0, X_1, X_2, X_3 是128位明文分成4个字。

第 i 轮(i=0..31)完成后,X_{i+4} 成为下一轮的左起第二个字。

32轮结束后,将最后四个字 X_35, X_34, X_33, X_32 反序输出,得到密文。

32轮迭代参数
项目值
轮数32
每轮输入4个32位字(Xi ~ Xi+3)
每轮输出1个32位字(Xi+4)
轮密钥长度32位
最终输出顺序反序:X35 X34 X33 X32

我的经验:初学时容易把轮序弄反。记住明文进的是X₀~X₃,最后反序输出,就对了。

3. 非线性变换 τ

τ 由4个并行的8×8 S盒构成。输入32位 B = b_0 || b_1 || b_2 || b_3,每个字节经过自己的S盒替换。

τ(B) = (Sbox[b_0], Sbox[b_1], Sbox[b_2], Sbox[b_3])

SM4的S盒是固定的256字节表。我曾在项目中用查表法直接实现,速度尚可;但如果要做硬件或并行加速,可以拆成4个独立表并预加载。

注意字节序:我个人踩过的坑——S盒输入字节顺序要严格按照 b_0 为最高字节。如果你的平台是大端,千万别搞反。

4. 线性变换 L

L 是对32位字做循环左移再异或。加密轮中使用的是:

L(B) = B ⊕ (B ≪ 2) ⊕ (B ≪ 10) ⊕ (B ≪ 18) ⊕ (B ≪ 24)

其中 ≪ 表示循环左移。这个操作扩散性极好,能把S盒的输出快速扩散到整个32位。

有意思的是,密钥扩展中的 L' 略有不同——移位量是13、23,而不是2、10、18、24。千万注意区分。

用途移位量
加密轮 L2, 10, 18, 24
密钥扩展 L'13, 23

一句话总结L:左移后与自己异或,让比特位相互影响。没有L,SM4就是4个独立S盒,脆弱得很。

5. 避坑指南

  • 循环左移别写成移位:我之前用C语言时把 rol 写成了 shl,结果所有轮结果都错了。一定要用循环移位。
  • S盒实现别用分支:有条件判断的S盒实现很慢。最好用查表,或者用SIMD gather指令。
  • 轮密钥顺序:轮密钥 rk_i 由密钥扩展生成,注意它是32位的,与 X_{i+1}⊕X_{i+2}⊕X_{i+3} 异或时是逐位异或。
  • 最终反序:代码里容易漏掉 memcpy 的顺序。密文顺序是 X_{35}, X_{34}, X_{33}, X_{32},不是 X_{32}~X_{35}。

我自己的测试习惯:写一个已知向量测试(比如国密标准里的示例数据),加密一轮就对比一轮的中间值。这样能快速定位是τ还是L的问题。

嗯,总结一下:SM4的每轮就是“加、代换、扩散、异或”四个动作,迭代32次。牢记S盒和L的移位量,轮密钥不要搞混,基本上就能写出正确的实现。