3. SM4 对称加密算法原理

SM4 是中国国家密码管理局发布的商用分组密码算法(原 SMS4),分组长度为 128 比特,密钥长度为 128 比特。SM4 采用非平衡 Feistel 结构,轮函数使用 32 轮迭代,加解密算法结构相同,仅轮密钥使用顺序相反。本章深入剖析 SM4 的 Feistel 结构、S 盒设计、密钥扩展算法、加密/解密流程及轮函数细节。

3.1 Feistel 结构与 SM4 的变体

SM4 采用 非平衡 Feistel 结构(Unbalanced Feistel Network),与经典 Feistel 结构(如 DES)的区别在于:每轮只处理 32 比特(1 个字),而非半个分组。这种设计使得 SM4 在硬件实现上更紧凑,轮函数更简洁。

Feistel 结构核心思想: 将数据分为左右两部分,每轮用轮函数 F 处理一部分并与另一部分异或,然后交换。SM4 的变体是每轮只更新一个 32 比特字,其余三个字直接移位。
SM4 非平衡 Feistel 结构(一轮) X0 (32bit) X1 (32bit) X2 (32bit) X3 (32bit) 轮函数 F rk X4 (32bit) X1 (32bit) X2 (32bit) X3 (32bit) 每轮只更新一个 32 比特字(X0),其余三个字(X1,X2,X3)直接右移一位。 32 轮后输出 (X32, X33, X34, X35),再经反序变换得到密文。

3.2 S 盒设计

SM4 的 S 盒是 8×8 的置换表,输入 8 比特,输出 8 比特。S 盒的设计基于 仿射变换 + 逆映射 的代数结构,具有以下特性:

  • 非线性度: 达到 112,抵抗线性密码分析。
  • 差分均匀性: 为 4,抵抗差分密码分析。
  • 代数次数: 为 7,抵抗高阶积分攻击。
  • 无不动点: S(a) ≠ a,∀a。
SM4 S 盒(十六进制表示)
行\列01234567 89ABCDEF
0D690E9FECCE13DB716B614C228FB2C05
12B679A762ABE04C3AA44132649860699
29C4250F491EF987A33540B43EDCFAC62
3E4B31CA9C908E89580DF94FA758F3FA6
44707A7FCF37317BA83593C19E6854FA8
5686B81B27164DA8BF8EB0F4B70569D35
61E240E5E6358D1A225227C3B01217887
7D40046579FD327524C3602E7A0C4C89E
8EABF8AD240C738B5A3F7F2CEF96115A1
9E0AE5DA49B341A55AD933230F58CB1E3
A1DF6E22E8266CA60C02923AB0D534E6F
BD5DB3745DEFD8E2F03FF6A726D6C5B51
C8D1BAF92BBDDBC7F11D95C411F105AD8
D0AC13188A5CD7BBD2D74D012B8E5B4B0
E8969974A0C96777E65B9F109C56EC684
F18F07DEC3ADC4D2079EE5F3ED7CB3948
实现提示: S 盒在软件实现中通常预计算为 256 字节的查找表,输入字节的高 4 位作为行索引,低 4 位作为列索引。例如 S(0xAB) = 表中第 A 行第 B 列 = 0x53。

3.3 密钥扩展算法

SM4 的密钥扩展算法将 128 比特(4 个字)的初始密钥扩展为 32 个 32 比特轮密钥(rk0 ~ rk31)。扩展过程使用与加密轮函数相同的非线性变换,但使用不同的系统参数和固定参数。

3.3.1 密钥扩展步骤

  1. 初始化: 将 128 比特密钥分为 4 个字 (MK0, MK1, MK2, MK3)。
  2. 异或系统参数: Ki = MKi ⊕ FKi,其中 FK 为系统参数(固定值)。
  3. 迭代生成: 对于 i = 0..31,计算 rki = Ki+4 = Ki ⊕ T'(Ki+1 ⊕ Ki+2 ⊕ Ki+3 ⊕ CKi)。

其中 T' 变换与加密轮函数中的 T 变换类似,但线性变换 L' 不同:L'(B) = B ⊕ (B <<< 13) ⊕ (B <<< 23)。

系统参数 FK 与固定参数 CK(部分)
参数值(十六进制)说明
FK0A3B1BAC6系统参数,用于密钥初始化
FK156AA3350
FK2677D9197
FK3B27022DC
CK000070E15固定参数,每轮不同,共 32 个
CK11C232A31
CK2383F464D
CK3545B6269

3.4 轮函数详解

SM4 的轮函数 F 是算法的核心,输入为 4 个字 (Xi, Xi+1, Xi+2, Xi+3) 和轮密钥 rki,输出一个 32 比特字。轮函数由三部分组成:异或合成、S 盒非线性变换、线性变换 L。

3.4.1 轮函数公式

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

其中 T 变换 = L(τ(B))
  τ(B) = (Sbox(B[0]), Sbox(B[1]), Sbox(B[2]), Sbox(B[3]))
  L(B) = B ⊕ (B <<< 2) ⊕ (B <<< 10) ⊕ (B <<< 18) ⊕ (B <<< 24)

3.4.2 轮函数内部流程

SM4 轮函数 F 内部结构 X1 X2 X3 rk B = X1⊕X2⊕X3⊕rk Sbox Sbox Sbox Sbox L 线性变换:B ⊕ (B<<<2) ⊕ (B<<<10) ⊕ (B<<<18) ⊕ (B<<<24)

3.5 加密/解密流程

3.5.1 加密流程

  1. 初始变换: 将 128 比特明文分为 4 个字 (X0, X1, X2, X3)。
  2. 32 轮迭代: 对于 i = 0..31,计算 Xi+4 = F(Xi, Xi+1, Xi+2, Xi+3, rki)。
  3. 反序变换: 输出密文 (Y0, Y1, Y2, Y3) = (X35, X34, X33, X32)。

3.5.2 解密流程

SM4 的解密与加密使用完全相同的算法结构,仅轮密钥使用顺序相反:加密使用 rk0, rk1, ..., rk31,解密使用 rk31, rk30, ..., rk0。这种设计使得加解密可以共用同一套硬件电路。

加解密一致性: SM4 的 Feistel 结构保证了加解密算法的对称性。解密时只需将轮密钥逆序输入,无需实现独立的解密轮函数。这是 Feistel 结构的重要优势。

3.5.3 加解密流程对比

步骤加密解密
输入128 比特明文 (X0~X3)128 比特密文 (Y0~Y3)
轮密钥顺序rk0, rk1, ..., rk31rk31, rk30, ..., rk0
轮函数F(Xi, Xi+1, Xi+2, Xi+3, rki)F(Xi, Xi+1, Xi+2, Xi+3, rk31-i)
轮数32 轮32 轮
反序变换(X35, X34, X33, X32)(X35, X34, X33, X32)
输出128 比特密文128 比特明文

3.6 代码实现示例

以下为 SM4 轮函数和加密核心的 Python 实现片段:

# SM4 S 盒(256 字节)
SBOX = [
    0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7,
    0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
    # ... 完整 S 盒见上表
    0x39, 0x48
]
def sm4_round_func(x0, x1, x2, x3, rk):
    """SM4 轮函数 F"""
    # 异或合成
    b = x1 ^ x2 ^ x3 ^ rk
    # S 盒非线性变换 τ
    t = 0
    for i in range(4):
        byte = (b >> (24 - i * 8)) & 0xFF
        t |= SBOX[byte] << (24 - i * 8)
    # 线性变换 L
    t ^= ((t << 2) | (t >> 30)) & 0xFFFFFFFF
    t ^= ((t << 10) | (t >>