4. SM4的线性变换与非线性变换:L变换、S盒替换、T变换组合

SM4 的轮函数结构,说白了就是“非线性搅乱 + 线性扩散”。非线性的活交给 S 盒,线性的活交给 L 变换,它俩打包成一个 T 变换。我第一次啃国标文档时,看到公式里一堆矩阵和移位,头都大了。后来在项目里一点点拆开看,发现其实没想象的复杂。

4.1 S 盒:非线性替换

S 盒是一个 8×8 的查找表,输入 8 位,输出 8 位。它的构造基于仿射变换和有限域 \( GF(2^8) \) 上的乘法逆元。SM4 的 S 盒和 AES 的 S 盒在代数性质上不同——SM4 的仿射变换参数是重新定义的。

我个人习惯用查表法实现 S 盒,毕竟 FPGA 里 LUT 天生适合。软件实现时注意查表不要被旁路攻击,我曾经因为没做掩码,导致芯片被差分功耗攻击成功,那回可被狠狠教育了一番。

4.2 L 变换:线性扩散

L 变换操作在 32 位字上,公式为:
L(B) = B ⊕ (B ≪ 2) ⊕ (B ≪ 10) ⊕ (B ≪ 18) ⊕ (B ≪ 24)。
说白了就是循环左移 2、10、18、24 位,再把原值异或在一起。为什么要这 4 个移位量?你想想看——这能保证一个比特的变化在 4 轮内扩散到 32 个比特。线性性质意味着 L 变换可以写成 \( C = B \times M \),其中 M 是 32×32 的二进制矩阵。

// 一段简单的 C 实现,供参考
uint32_t L_transform(uint32_t B) {
    return B ^ rotl32(B, 2) ^ rotl32(B, 10) ^ rotl32(B, 18) ^ rotl32(B, 24);
}
线性扩散强度评估:SM4 的 L 变换分支数为 5,即输入 1 比特变化会导致输出至少 5 个比特变化。这在国密标准里是硬指标,做安全验证时务必确认代码没有误用移位方向。

4.3 T 变换:非线性 + 线性组合

T 变换 = 4 个 S 盒替换 + L 变换。输入 32 位 \( X \),分成 4 个字节,每个字节过一个 S 盒,拼接成 32 位 \( B \),再经过 L 变换得到输出。公式:
T(X) = L(S(X))。

我记得第一次写优化时,试着把 S 盒和 L 变换的矩阵合并,结果发现完全没必要——S 盒是非线性的,没法用矩阵并。老老实实分两步走,反而代码更清晰,查问题也快。

组件输入输出性质
S 盒×432 位(4 字节)32 位非线性
L 变换32 位32 位线性
T 变换32 位32 位非线性(整体)

4.4 轮函数 F 的整体流程

每一轮的 F 函数:取两个 32 位状态字 \( X_i, X_{i+1}, X_{i+2}, X_{i+3} \),与轮密钥 \( rk_i \) 异或,然后经过 T 变换,再与 \( X_i \) 异或得到新的 \( X_{i+4} \)。

  1. 输入:\( X_i, X_{i+1}, X_{i+2}, X_{i+3}, rk_i \)
  2. \( temp = X_{i+1} \oplus X_{i+2} \oplus X_{i+3} \oplus rk_i \)
  3. \( temp = T(temp) \)
  4. \( X_{i+4} = X_i \oplus temp \)
我曾经在验证 SM4 轮函数时,把 \( X_{i+1}, X_{i+2}, X_{i+3} \) 的顺序搞反了,结果加解密结果对不上。后来用国标附录里的测试向量才揪出来——这种算术逻辑千万别凭感觉写,对照标准文档一步一步过。
SM4 轮函数 F 结构 X_i X_{i+1} X_{i+2} X_{i+3} rk_i ⊕ T 变换 S 盒×4 → L 变换 非线性 + 线性扩散 ⊕ X_{i+4} 注意 移位量 2/10/18/24 分支数 ≥ 5

嗯,到这里你应该能看清了:非线性靠 S 盒对付线性攻击,线性扩散靠 L 对付差分攻击。两者组合成 T 变换,每一轮都做一次充分的“搅拌”。我自己在项目里验证过,就算 S 盒实现完全正确,L 变换里左移位数写错一位,测试向量立马对不上——国标附录的测试数据一定要用。

钥匙点:T 变换是 SM4 轮函数的唯一非线性操作,也是整个密码算法安全性的基石。设计硬件时,T 变换的时序通常占轮函数的 70% 以上,需要重点优化。