SM4 对称加密算法(上):从背景到核心流程
聊到国密算法,SM4 是绕不开的一块基石。我最早接触它是在一个政务系统的数据加密项目里,客户要求所有存储的敏感字段必须用国密加密。当时第一反应是「用 AES 不也挺好?」——后来才明白,SM4 的设计其实有很多巧思,值得细细拆解。
SM4 算法背景与设计思想
SM4 的前身叫 SMS4,2006 年首次公开,2012 年正式成为国家标准(GB/T 32907-2016)。它是分组对称加密算法,分组长度 128 位,密钥长度也是 128 位。说白了,就是你把数据切成 16 字节一块,用 16 字节的密钥去加密。
你可能会问:为什么不用 AES?嗯,这里有个现实问题。AES 是美国标准,在一些合规场景里(比如政务、金融、军工),国密是硬性要求。我遇到过一家银行,因为跨境业务需要同时支持 AES 和 SM4,结果发现 SM4 的轮函数结构在硬件实现上反而更省资源——这是后话。
SM4 的设计思想可以概括为三点:
- Feistel 结构:和 DES 类似,但轮数更多(32 轮),安全性更高。
- 面向硬件优化:S 盒和线性变换都考虑了硬件实现的效率。
- 密钥扩展与加解密共用:解密时只需要把轮密钥逆序使用,不需要额外计算。
核心要点:SM4 不是 AES 的简单模仿。它的 S 盒、线性变换、密钥扩展都有独立的设计逻辑,理解这些才能用好它。
SM4 加解密流程详解
SM4 的加解密流程可以拆成三块:密钥扩展、轮函数、反序变换。我们一个一个来看。
1. 密钥扩展:从 128 位主密钥到 32 个轮密钥
主密钥 MK 是 128 位,拆成 4 个 32 位字:(MK0, MK1, MK2, MK3)。然后通过一个固定的系统参数 FK 和 CK 来生成轮密钥。
具体步骤:
- 计算 K0 = MK0 ⊕ FK0, K1 = MK1 ⊕ FK1, K2 = MK2 ⊕ FK2, K3 = MK3 ⊕ FK3
- 对于 i = 0 到 31:
rki = Ki+4 = Ki ⊕ T'(Ki+1 ⊕ Ki+2 ⊕ Ki+3 ⊕ CKi)
这里的 T' 是一个非线性变换,和加密轮函数里的 T 很像,但线性部分不同。我刚开始看的时候觉得这有点绕,后来写代码调试才发现,密钥扩展其实可以提前算好存起来,加解密时直接查表就行。
实战建议:在嵌入式设备上,密钥扩展的计算量可以提前到初始化阶段完成,避免每次加解密都重复计算。我在一个 IoT 项目里就是这么做的,性能提升了约 30%。
2. 轮函数:32 轮迭代的核心
每一轮的结构很简单:输入是 4 个 32 位字 (X0, X1, X2, X3),输出是 (X1, X2, X3, X4),其中 X4 = X0 ⊕ T(X1 ⊕ X2 ⊕ X3 ⊕ rki)。
这个 T 变换包含两步:
- S 盒替换:把 32 位拆成 4 个字节,每个字节通过 S 盒映射成新字节。
- 线性变换 L:对 32 位结果做循环移位和异或操作。
L 变换的公式是:L(B) = B ⊕ (B <<< 2) ⊕ (B <<< 10) ⊕ (B <<< 18) ⊕ (B <<< 24)。
说白了,就是通过移位和异或把 S 盒的输出扩散到整个 32 位空间。这个设计比 AES 的 MixColumns 更简单,硬件实现时只需要几个移位器和异或门。
注意:SM4 的轮函数里没有字节代换后的行移位和列混合,而是直接用线性变换 L 完成扩散。这个差异在软件实现时要注意,别和 AES 搞混了。
3. 反序变换:最后一步的巧妙设计
32 轮迭代结束后,会得到 (Y0, Y1, Y2, Y3)。但直接输出的话,解密时会有问题。所以 SM4 加了一个反序变换:输出 (Y3, Y2, Y1, Y0)。
为什么这么做?因为 SM4 是 Feistel 结构,解密时只需要把轮密钥逆序使用,加解密流程完全一样。反序变换保证了加解密的一致性。我第一次实现时忘了这一步,结果解密出来的数据全是乱码——嗯,这个坑我踩过。
SM4 的 S 盒设计与线性变换
S 盒是 SM4 唯一的非线性部件,它的设计直接决定了算法的安全性。SM4 的 S 盒是一个 8×8 的置换表,输入 8 位,输出 8 位。具体数值可以在标准文档里查到,我这里不贴完整表了,但有几个特点值得注意:
- 代数结构:基于有限域 GF(2^8) 上的仿射变换和乘法逆元构造。
- 差分均匀性:最大差分概率为 2^{-6},能抵抗差分密码分析。
- 线性逼近:最大线性偏差为 2^{-4},能抵抗线性密码分析。
线性变换 L 的设计也有讲究。它的分支数为 5,意味着任意 1 个字节的变化至少会扩散到 5 个字节。这个指标比 AES 的 MixColumns(分支数 5)一样,但实现更简单。
一句话总结:SM4 的 S 盒负责「混淆」,线性变换负责「扩散」,两者配合实现了香农提出的密码设计两大原则。
SM4 整体流程框架图
下面这张图展示了 SM4 加密的完整流程,从密钥扩展到 32 轮迭代,再到反序输出。你可以对照着看,每一步的位置和作用一目了然。
实战中的几个注意点
讲完理论,说几个我在项目中踩过的坑:
- 轮密钥不要搞错顺序:加密时用 rk0 到 rk31,解密时用 rk31 到 rk0。我见过有人解密时忘了逆序,结果 debug 了一整天。
- S 盒查表要小心:SM4 的 S 盒是 256 字节的表,用 C 语言实现时记得用 const 数组,别放在栈上。
- 线性变换的移位位数:2、10、18、24,这四个数字是固定的,别记错了。我曾经把 18 写成 16,结果加解密对不上。
小技巧:如果你在 ARM Cortex-M 系列芯片上实现 SM4,可以利用它的 barrel shifter 硬件特性,把线性变换的移位和异或合并成一条指令,能省不少周期。