2. SM4 算法核心原理:轮函数、S盒与密钥扩展

好,咱们直接切入正题。
SM4 算法,说白了就是一种分组密码。跟 AES 类似,它把数据切成 128 位一块,然后用一个 128 位的密钥去加密。
我个人习惯,先不看那些复杂的数学公式,先搞清楚它的骨架——轮函数、S 盒、密钥扩展。这三个东西搞明白了,整个算法基本就通了。

2.1 整体结构:一种“非线性-线性-异或”的迭代结构

SM4 用的是 32 轮迭代 的结构。
每一轮做的事情,核心就三个步骤:

  1. 非线性变换:通过 S 盒做字节替换,引入混淆(Confusion)。
  2. 线性变换:通过移位和异或,把局部的影响扩散到整个分组(Diffusion)。
  3. 轮密钥加:把这一轮的密钥异或进去。

你可以这么理解:一轮就是一个“搅拌”过程。32 轮下来,明文和密钥的关系被彻底打乱。

我曾经踩过的一个坑: 刚接触 SM4 时,我默认它跟 AES 一样,最后一轮会少做一次线性变换。结果调了一整天,加密结果死活对不上标准测试向量。后来查了规范才发现,SM4 的 32 轮操作是完全对称的,每一轮结构一模一样。嗯,这个细节一定要注意。

2.2 轮函数 F:每一轮的核心运算

轮函数 F 的输入是 4 个 32 位的字(X0, X1, X2, X3),加上一个 32 位的轮密钥 rk。
输出是一个 32 位的字。它的数学表达式很简单:

F(X0, X1, X2, X3, rk) = X0 ⊕ T(X1 ⊕ X2 ⊕ X3 ⊕ rk)

说白了,就是三步走:

  • 先做一次异或:把 X1, X2, X3 和轮密钥 rk 异或起来。
  • 然后走 T 变换:这个 T 是“非线性 + 线性”的组合。
  • 最后再跟 X0 异或:得到本轮的输出。

这里的 T 变换,是 SM4 的精髓。我单独拆开讲。

2.3 T 变换:S盒 + L变换

T 变换由两部分串联:

2.3.1 非线性变换 τ:S 盒的天地

τ 变换把输入的 32 位,拆成 4 个 8 位的字节。每个字节单独查一个固定的 S 盒。
这个 S 盒是 SM4 算法设计好的,一个 8x8 的替换表。你想想看,它定义好了每个输入字节应该替换成什么输出字节。

为什么要有 S 盒?
为了防止代数攻击。如果全是线性运算,那这个密码就太脆弱了。S 盒是非线性的,它是整个算法安全性的基石。

实现上的小技巧: 在实际编码中,我习惯把 S 盒定义成一个 256 字节的数组。查表时直接索引,性能非常高。

2.3.2 线性变换 L:把雪崩效应拉满

τ 变换的输出是一个 32 位的字 B。对它再做一次线性变换 L:

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

这里 <<< 表示 32 位的循环左移。你看,就 4 次异或,3 次移位。你看明白了吗?这步操作的目的,就是让 S 盒替换后的任意一个比特变化,能够迅速扩散到整个 32 位输出。这就是所谓的“雪崩效应”。

所以,轮函数 F 的整体流程,可以用一个简单的图来表示:

SM4轮函数F结构图 SM4 轮函数 F 结构图 X1 (32bit) X2 (32bit) X3 (32bit) X0 (32bit) rk (32bit) ⊕ S 盒 (τ 变换) 4 个 S 盒并行 (8bit → 8bit) 引入非线性 → 32bit 输出 B L 线性变换 循环左移 + 异或 → 雪崩效应 ⊕ 本轮输出 (32bit) 流程:⊕ → S盒 → L变换 → ⊕

2.4 S 盒:算法的“保险柜”

SM4 的 S 盒是固定的,由算法设计者用某种特定的数学方法构造出来。
我直接给你看它的真值表(部分),方便你理解数据结构:

输入 (十六进制) 输出 (十六进制) 备注
0x00 0xD6 查表:第 0 行第 0 列
0x10 0x5E 查表:第 1 行第 0 列
0x55 0xBE 一个中间结果
0xFF 0x7A 最后一个值
为什么S盒的设计重要? 你可以把S盒想象成一个锁芯的凹凸结构。它决定了这个密码算法抵抗线性密码分析和差分密码分析的能力。SM4 的 S 盒经过精心设计,能够保证足够的安全冗余。

2.5 密钥扩展:从 128 位主密钥到 32 个轮密钥

这是很多人容易搞混的地方。SM4 的主密钥是 128 位的,但加密过程需要 32 个 32 位的轮密钥。怎么来的?靠密钥扩展算法算出来的。
密钥扩展跟加密过程的结构非常相似,可以说是“加密过程的镜像”。

步骤是这样的:

  1. 把 128 位主密钥 MK 拆成 4 个 32 位字:MK0, MK1, MK2, MK3。
  2. 跟 4 个系统参数 FK 异或,得到初始状态 K0, K1, K2, K3。
    • FK0 = 0xA3B1BAC6
    • FK1 = 0x56AA3350
    • FK2 = 0x677D9197
    • FK3 = 0xB27022DC
  3. 然后迭代 32 轮,生成轮密钥 rk0 ~ rk31。每一轮的公式是:
    K_{i+4} = K_i ⊕ T'(K_{i+1} ⊕ K_{i+2} ⊕ K_{i+3} ⊕ CK_i)

这里的 T' 变换,跟加密用的 T 变换基本一样。唯一的区别是,T' 中的线性变换 L 不一样。加密时用的 L,而密钥扩展用的是 L':

L'(B) = B ⊕ (B <<< 13) ⊕ (B <<< 23)

看到了吗?就是移位参数不同。加密用 2, 10, 18, 24;密钥扩展用 13, 23。我刚开始看的时候,心里也犯嘀咕:为什么要设计两套参数?后来一想,这是为了安全。你想想看,如果加密和密钥扩展的结构完全一样,就可能会产生一种叫“相关密钥攻击”的漏洞。现在这样错开,就安全多了。

我曾经遇到过的问题是: 一开始我嫌麻烦,直接用加密部分的 T 变换来做密钥扩展。结果跑出来的轮密钥全不对!别问我怎么知道的...那次排查花了我两个小时。所以这里必须强调:加密和密钥扩展,请一定要分开实现 L 和 L'。

CK_i 是一组 32 个固定的常数,由算法定义。它们是按某种特定规律生成的,你直接用一个数组存起来就行。

2.6 总结一下这一块的核心

看完上面的内容,你可能觉得有点多。别急,我用一张对照表帮你收个尾:

核心组件 输入 输出 作用
S 盒 (τ) 8 位 8 位 提供非线性,抵抗攻击
L 变换 (加密) 32 位 32 位 提供扩散,产生雪崩效应
L' 变换 (密钥扩展) 32 位 32 位 保证轮密钥之间没有相关性
轮函数 F 4×32bit + 1×32bit 32 位 完成一轮核心混合操作

嗯,现在再看“SM4 算法原理”,是不是感觉清晰了很多?
说白了,它就是围绕着一个精心设计的 S 盒,加上两组不同的线性变换,反复迭代 32 次。把这个框架刻在脑子里,后面写代码就不会跑偏了。