3. 国密算法原理(一):SM4对称加密算法详解

说起对称加密,大家可能首先想到 AES。但在咱们国密体系里,SM4 才是正儿八经的对称加密标准。这东西在金融、政务、物联网里用得特别多。

SM4 从 2006 年发布到现在,已经被写入国标 GM/T 0002-2012。它和 AES 类似,都属于分组密码。分组长度固定 128 位,密钥长度也是 128 位。我刚开始接触时觉得这参数和 AES 差不多,但深入一看,里面的设计思路还真不一样。

3.1 SM4 的基本结构

SM4 的核心是个 32 轮的非平衡 Feistel 网络结构。啥意思呢?说白了,就是每轮只处理一半的数据。我习惯把它的结构拆成三块来看:

  • 密钥扩展部分:把 128 位的主密钥,扩展成 32 个 32 位的轮密钥
  • 加密/解密轮函数:每一轮用特定的非线性变换和线性变换
  • 反序变换:最后一轮之后,把输出做个反序处理

我个人觉得,SM4 设计上的聪明之处在于,加密和解密用的是同一套轮密钥——只是顺序相反。这跟 AES 那种加密解密逻辑不同的设计比,硬件实现上能省不少事儿。

实用提示:我在项目中遇到过,有些工程师刚上手 SM4 时,把解密密钥顺序弄反了。记住,加密用第 1 轮密钥,解密就得用第 32 轮密钥。这个顺序颠倒了,结果全错。

3.2 密钥扩展算法

为什么要设计这么复杂的密钥扩展算法?你想想看,如果轮密钥之间相关性太强,攻击者就能从某几轮信息推导出其他轮次的密钥,那整个密码就废了。

SM4 的密钥扩展分三步:

  1. 将 128 位主密钥分成 4 个 32 位的字,记为 MK0、MK1、MK2、MK3
  2. 用固定常数 FK 做异或,得到初始状态 K0~K3
  3. 然后迭代 32 次,每次生成一个轮密钥 rk[i]

具体的计算公式是这样的:

K[0] = MK[0] ^ FK[0]
K[1] = MK[1] ^ FK[1]
K[2] = MK[2] ^ FK[2]
K[3] = MK[3] ^ FK[3]

for i = 0 to 31:
    rk[i] = K[i+4] = K[i] ^ T'(K[i+1] ^ K[i+2] ^ K[i+3] ^ CK[i])

那个 CK 是固定的 32 个常数,每个值都有讲究。我查过这些常数的生成方式,其实是从一个数学序列里截出来的。说白了就是确保轮密钥足够“随机”。

轮密钥扩展常量 CK_i(前 8 轮示例,16 进制)
iCK[i]iCK[i]
000070e154001c232a
1000e151c500232a31
200151c236002a3138
3001c232a700313f46

3.3 加密与解密流程

嗯,这里要注意。SM4 的加密流程看着挺长,但每一轮做的事其实很简单。我画过一张流程图,讲课时总拿出来用:

核心理解:SM4 每轮只修改 128 位数据中的一半(32 位),另外 96 位原样传递。这种设计让硬件实现非常规整,也方便做并行处理。

具体的加密流程可以这样理解:

  1. 输入 128 位明文,拆成 4 个 32 位字 X0、X1、X2、X3
  2. 对于 i = 0 到 31:
    X[i+4] = F(X[i], X[i+1], X[i+2], X[i+3], rk[i])
  3. 最后输出反序:(Y0, Y1, Y2, Y3) = (X35, X34, X33, X32)

这里的 F 函数,就是 SM4 的精髓。它长这样:

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

那个 T 函数是个合成置换,里头又包了两层:一个非线性变换 τ,和一个线性变换 L。τ 用 4 个并行的 S 盒(就是那张 8x8 的查表),L 只是简单的移位异或。

我记得当初研究这个 S 盒时,发现设计得很巧妙。它满足了一系列密码学性质,比如差分均匀性、非线性度等等。说白了,这个 S 盒就是 SM4 安全性的第一道防线。

避坑指南:我曾经在一家金融客户现场排查过这类问题。他们软件里的 SM4 S 盒有一个字节写错了(0x8D 写成了 0x8C)。结果加密出来的数据,前几轮还正常,后几轮完全走样。这种缺陷测试用例还查不出来,必须做已知向量测试才能发现。

3.4 代码实现要点

下面是我整理的一个参考代码框架。实际项目中,S 盒会用常量数组预定义,这里只展示核心逻辑:

#define SM4_NUM_ROUNDS 32

// 密钥扩展
void sm4_key_expand(uint32_t* key, uint32_t* rk) {
    uint32_t K[36];
    K[0] = key[0] ^ FK[0];
    K[1] = key[1] ^ FK[1];
    K[2] = key[2] ^ FK[2];
    K[3] = key[3] ^ FK[3];
    
    for (int i = 0; i < 32; i++) {
        rk[i] = K[i+4] = K[i] ^ T_prime(
            K[i+1] ^ K[i+2] ^ K[i+3] ^ CK[i]
        );
    }
}

// 轮函数 F 中的合成置换 T
uint32_t sm4_t(uint32_t X) {
    uint8_t* x = (uint8_t*)&X;
    uint32_t Y = (S[x[0]] << 24) | 
                 (S[x[1]] << 16) | 
                 (S[x[2]] << 8)  | 
                  S[x[3]];
    // 线性变换 L
    return Y ^ (Y << 2) ^ (Y << 10) ^ 
           (Y << 18) ^ (Y << 24);
}

// 加密 128 位数据块
void sm4_encrypt(uint32_t* X, uint32_t* rk, uint32_t* output) {
    uint32_t state[36];
    state[0] = X[0]; state[1] = X[1];
    state[2] = X[2]; state[3] = X[3];
    
    for (int i = 0; i < SM4_NUM_ROUNDS; i++) {
        state[i+4] = state[i] ^ sm4_t(
            state[i+1] ^ state[i+2] ^ state[i+3] ^ rk[i]
        );
    }
    output[0] = state[35]; output[1] = state[34];
    output[2] = state[33]; output[3] = state[32];
}

3.5 安全性分析与实际应用

SM4 的安全性,到现在为止经过了不少密码学界的“拷问”。目前公开的密码分析中,最好的攻击也只能攻到 22 轮,离 32 轮还差着 10 轮呢。这个安全余量是够的。

不过,有几点我建议做合规治理的同学要注意:

  • ECB 模式慎用:分组密码加密相同明文块会得到相同密文块,容易泄露模式信息
  • CBC 模式要有随机 IV:IV 一定要一次一密,或者用 HKDF 生成
  • GCM 模式优先:能同时提供加密和认证,省掉额外做 MAC 的麻烦

小经验:我参与的某个政务云项目里,最初用的是 ECB 模式加密身份证号。做等保测评时,安全专家一眼就看出了问题——因为密文里头有些字段总是相同的。后来果断换了 GCM 模式,问题解决。

3.6 SM4 与其他国密算法的关系

在实际系统中,SM4 很少单独使用。它通常和 SM2(公钥加密/签名)、SM3(哈希)一起构成完整的密码套装。

我举个例子:一个典型的数据安全传输流程是这样的:

  1. 用 SM2 协商出对称密钥
  2. 用 SM4 加密实际数据
  3. 用 SM3 校验数据完整性

这三者缺一不可。有些厂商只实现了 SM4 就说“支持国密”,其实是不合规的。我在做合规治理时,经常会检查这些“短斤缺两”的实现。

下面这幅图展示了 SM4 加密/解密在整体国密体系中的位置和流程——从密钥产生到密文输出的完整链路:

SM4 加密/解密流程与国密体系关系图 128 位主密钥 MK 固定常数 FK 密钥扩展算法 T'( · ) + S 盒 + CK 常数 32 轮密钥 rk[0..31] 128 位明文 X 32 轮迭代加密 F(·) 轮函数 第 i 轮: X[i+4]=X[i]^T(X[i+1]^X[i+2]^X[i+3]^rk[i]) 其中 T=τ∘L,τ=S-box×4,L=移位异或 反序输出 128 位密文 Y 注意:解密时用相同轮密钥 顺序逆置,算法逻辑相同 加密用 rk[0]~rk[31],解密相反

说实话,SM4 的设计是非常扎实的。它既考虑了密码强度,也兼顾了工程实现的效率。我在做国密合规治理时,经常跟客户说:别觉得 SM4 跟 AES 比有什么劣势。对于绝大多数业务场景,SM4 的 128 位密钥配合 32 轮迭代,已经提供了足够的安全保障。