第二章:SM4加密解密原理归纳总结

SM4,也叫国密对称算法。说白了,就是一种分组加密标准。国内很多政务系统、金融核心都在用。我最早接触它是在一个数据交换平台项目,对方要求必须用国密,不能用AES。那时候我连夜啃了SM4标准文档,印象很深。

这一章,我把SM4的核心原理掰开揉碎,结合我踩过的坑一起聊。你看完就能明白它到底怎么加密、怎么解密,结构长什么样。

SM4的整体架构

SM4是一种128位分组、128位密钥的Feistel结构密码。分组长度128位,密钥128位。轮函数迭代32轮。解密时,把32轮的子密钥倒过来用就行。

先给你看一张架构图,把整个流程串起来:

SM4加密流程框架 128位明文分组 拆分为4个32位字 (X0, X1, X2, X3) 32轮Feistel迭代 每轮核心:轮函数F 合成置换 T = L(τ(X)) τ:4个S盒并行非线性变换 L:32位线性变换(循环左移+异或) 反序变换 128位密文分组 密钥扩展 每轮子密钥rk_i 32轮共用

嗯,这张图其实已经把加密主体说清了。密钥扩展在右边,生成32个子密钥,每轮喂给轮函数。解密就是把加密的流程倒着跑一遍,子密钥反序。

密钥扩展:从128位主密钥生成32个rk

密钥扩展是SM4里的关键一步。算法用128位初始密钥MK,通过系统参数FK和固定参数CK,迭代出32个32位轮密钥rk。

核心公式(算法描述)

K[0] = MK[0] ⊕ FK[0]

K[1] = MK[1] ⊕ FK[1]

K[2] = MK[2] ⊕ FK[2]

K[3] = MK[3] ⊕ FK[3]

对 i = 0..31:

rk[i] = K[i+4] = K[i] ⊕ T'(K[i+1] ⊕ K[i+2] ⊕ K[i+3] ⊕ CK[i])

这里T'和加密轮函数里的T略有不同,线性变换部分换成了简单的循环左移+异或。我个人习惯把这两套S盒和参数表直接内联到代码里,避免运行时查表出问题。

个人经验

我在一个嵌入式项目中,因为Flash空间有限,把S盒压缩成函数计算,结果跑出来的密文全错。后来才意识到S盒是预定义查表,不能算。老老实实把256字节S盒放ROM里就正常了。

轮函数F:每轮的核心变换

每轮加密时,输入4个32位字 (X[i], X[i+1], X[i+2], X[i+3]),输出一个新字。公式很简单:

X[i+4] = X[i] ⊕ T( X[i+1] ⊕ X[i+2] ⊕ X[i+3] ⊕ rk[i] )

里面的T函数分两步:

  • 非线性变换τ:把32位拆成4个字节,每个字节过S盒(查表),输出4个新字节拼回32位。
  • 线性变换L:对τ的结果做循环左移和异或: L(B) = B ⊕ (B<<<2) ⊕ (B<<<10) ⊕ (B<<<18) ⊕ (B<<<24)

说白了,这就是一个“非线性+线性”的组合,让每轮都产生足够的混淆和扩散。

为什么会这样设计?我理解是为了抗差分分析和线性分析。两个S盒加一个L,32轮下来,任何一位的微小变化都能扩散到整个分组。

我曾经踩过的坑

有一次我把L变换里的循环左移位数写反,写成右移。调试了整整一个下午,对比标准向量才抓出来。嗯,移位方向一定要对着标准文档反复检查。

解密过程:密钥反序就行

SM4的解密非常对称。加密时用子密钥顺序:rk[0], rk[1], ..., rk[31]。解密时就用反序:rk[31], rk[30], ..., rk[0]。轮函数F和其他流程完全一样。

注意,这里的“反序”不是位运算反转,就是数组下标反过来。代码实现里特别容易写成索引从0到31,结果解密失败。

关键参数速查表

参数 值/说明
分组长度 128 bit(16字节)
密钥长度 128 bit
迭代轮数 32 轮
S盒 8×8 S盒,256字节,固定查表
系统参数FK FK[0]=A3B1BAC6, FK[1]=56AA3350, FK[2]=677D9197, FK[3]=B27022DC
固定参数CK 32个32位值,由特定公式生成
线性变换L(加密) L(B)=B⊕(B<<<2)⊕(B<<<10)⊕(B<<<18)⊕(B<<<24)
线性变换L'(密钥扩展) L'(B)=B⊕(B<<<13)⊕(B<<<23)

你想想看,这么多参数,任何一个地方出错,测出来的密文就和标准对不上。我当年调试的时候,先用国家标准文档里的测试向量,逐轮输出中间值对比,才能定位问题。

代码核心片段(Python实现思路)

下面是一段简化版的轮函数实现,不是完整工程代码,但能体现核心逻辑:

def sm4_round(x0, x1, x2, x3, rk):
    """
    一轮SM4加密
    输入: 4个32位整数, 1个轮密钥
    返回: 下一个32位字
    """
    temp = x1 ^ x2 ^ x3 ^ rk
    # 非线性变换τ:4个字节查S盒
    s_out = sbox[(temp >> 24) & 0xFF] << 24
    s_out |= sbox[(temp >> 16) & 0xFF] << 16
    s_out |= sbox[(temp >> 8) & 0xFF] << 8
    s_out |= sbox[temp & 0xFF]
    # 线性变换L
    out = s_out ^ rotl(s_out, 2) ^ rotl(s_out, 10) ^ rotl(s_out, 18) ^ rotl(s_out, 24)
    return x0 ^ out

建议

如果你在写性能敏感的代码(比如并发高吞吐场景),可以考虑用bitslice或者SIMD优化S盒查表。不过那是后话了,先把基础原理跑通最重要。

总结

SM4原理并不复杂。记住三点:

  • Feistel结构,32轮迭代。
  • 轮函数F = 非线性S盒 + 线性移位异或。
  • 解密就是密钥反序,其他一模一样。

我个人觉得,SM4的简洁度比AES还好理解。下一阶我们会拿它做并发监控和容量审计,所以这章把加密解密原理吃透,后面代码才不会跑偏。