第二章:SM4加密解密原理归纳总结
SM4,也叫国密对称算法。说白了,就是一种分组加密标准。国内很多政务系统、金融核心都在用。我最早接触它是在一个数据交换平台项目,对方要求必须用国密,不能用AES。那时候我连夜啃了SM4标准文档,印象很深。
这一章,我把SM4的核心原理掰开揉碎,结合我踩过的坑一起聊。你看完就能明白它到底怎么加密、怎么解密,结构长什么样。
SM4的整体架构
SM4是一种128位分组、128位密钥的Feistel结构密码。分组长度128位,密钥128位。轮函数迭代32轮。解密时,把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还好理解。下一阶我们会拿它做并发监控和容量审计,所以这章把加密解密原理吃透,后面代码才不会跑偏。