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 的仿射变换参数是重新定义的。
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);
}
4.3 T 变换:非线性 + 线性组合
T 变换 = 4 个 S 盒替换 + L 变换。输入 32 位 \( X \),分成 4 个字节,每个字节过一个 S 盒,拼接成 32 位 \( B \),再经过 L 变换得到输出。公式:T(X) = L(S(X))。
我记得第一次写优化时,试着把 S 盒和 L 变换的矩阵合并,结果发现完全没必要——S 盒是非线性的,没法用矩阵并。老老实实分两步走,反而代码更清晰,查问题也快。
| 组件 | 输入 | 输出 | 性质 |
|---|---|---|---|
| S 盒×4 | 32 位(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} \)。
- 输入:\( X_i, X_{i+1}, X_{i+2}, X_{i+3}, rk_i \)
- \( temp = X_{i+1} \oplus X_{i+2} \oplus X_{i+3} \oplus rk_i \)
- \( temp = T(temp) \)
- \( X_{i+4} = X_i \oplus temp \)
嗯,到这里你应该能看清了:非线性靠 S 盒对付线性攻击,线性扩散靠 L 对付差分攻击。两者组合成 T 变换,每一轮都做一次充分的“搅拌”。我自己在项目里验证过,就算 S 盒实现完全正确,L 变换里左移位数写错一位,测试向量立马对不上——国标附录的测试数据一定要用。