SM4算法原理:分组密码结构、密钥扩展、轮函数、ECB/CBC/CTR模式
各位好,咱们今天聊 SM4 算法。这是国密标准里的对称密码,说白了就是加密和解密用同一把钥匙。我在项目中经常用它来保护通信数据,尤其是和硬件加密卡打交道时,SM4 的出镜率特别高。
很多人觉得对称密码无非就是分组、轮函数、密钥扩展,没什么新鲜的。嗯,你理解得没错,但具体到每个细节,还是有不少坑的。我刚开始用 SM4 时,就因为在 OFB 模式里初始化向量复用错了,导致整批密文出问题。所以咱还是从原理到模式,一步步捋清楚。
分组密码结构:Feistel 网络
SM4 用的是 Feistel 网络结构,这个结构挺有意思。它将 128 位明文分成左右两半,每轮只处理一半的数据,另一半原封不动地交换到下一轮。这样做的好处是加解密可以用完全相同的代码逻辑,只是密钥使用顺序相反而已。
- 分组大小:固定 128 位
- 密钥长度:也是 128 位
- 轮数:32 轮,不少吧?这就保证了足够的安全性
- 每轮结构:使用轮密钥对右半部分进行变换,结果与左半部分异或,然后左右交换
为什么分组大小必须是 128 位?我记得在早期的一些项目中,还在用 64 位分组的 DES,现在看那就是给攻击者留后门。128 位是目前对称密码的标配,兼顾了效率和安全性。
核心点:SM4 的 Feistel 结构让加解密实现变得非常简单。我在封装 SDK 时,直接用同一套函数处理加密和解密,只要把轮密钥序列反过来就行。这个设计真的很聪明。
密钥扩展:固定参数与循环移位
密钥扩展,说白了就是怎么把自己的 128 位密钥,变出 32 个子密钥来。SM4 的方法很巧妙,它使用了固定的系统参数和固定的常量。
具体步骤大概是:
- 把 128 位密钥分成 4 个 32 位字,依次记为 MK0、MK1、MK2、MK3
- 用系统参数 FK0、FK1、FK2、FK3 和上面四个字做异或,得到初始的 K0、K1、K2、K3
- 从第 4 轮开始,用固定的常量 CK 和循环左移操作,逐个生成 rk0 到 rk31
我的小技巧:在实际编码时,建议将轮密钥一次性扩展完,存到一个数组里。否则每轮都重新算一遍,性能会很难看。我曾在一个嵌入式项目里踩过这个坑,32 轮全部实时扩展,MCU 直接卡死在加解密上。
| 参数 | 值 | 说明 |
|---|---|---|
| FK0 | 0xA3B1BAC6 | 系统参数 |
| FK1 | 0x56AA3350 | 系统参数 |
| FK2 | 0x677D9197 | 系统参数 |
| FK3 | 0xB27022DC | 系统参数 |
| CK[i] | 固定表 | 32 个常量,每个 32 位 |
轮函数:S 盒与线性变换
SM4 的轮函数包括三个关键操作:S 盒替换、线性变换 L 和轮密钥异或。你想想看,每一步都有自己的讲究。
- S 盒:8 位输入、8 位输出,是一个固定的置换表。我个人习惯在代码里用预计算的数组
- 线性变换 L:将 S 盒输出再次做循环移位和异或,增加扩散性
- 轮密钥异或:将当前轮的子密钥与状态数据异或,这是 Feistel 网络里的常规操作
注意:SM4 的 S 盒是国标里规定死的,不允许任何人动。但 S 盒的设计其实很讲究,它有一定的代数性质,能抵抗差分分析和线性分析。
避坑指南:不要试图自作聪明去修改 S 盒!我曾经在实验阶段试过用自己的查找表,结果加密出来的结果完全不对。后来花了足足两天才发现是 S 盒写错了一位。SM4 的 S 盒是标准,不是建议。
ECB / CBC / CTR 模式
讲完基础结构,咱聊聊工作模式。SM4 本身只能处理 128 位分组,但实际数据哪有那么巧?数据长度可能不是 128 位的整数倍。这时候就需要模式来定规矩了。
ECB 模式:简单但不安全
ECB 模式最简单:把数据切成 128 位一块,每块独立加密。我刚开始学密码时觉得这模式挺好,但后来做项目发现它问题很大。重复的明文块会产生相同的密文块,这在图像、文件等有规律的数据里会暴露信息。
说实话,我建议你在生产环境里尽量别用 ECB。除非数据本身就已经随机化,比如加密硬编码的序列号。
CBC 模式:最常用的模式
CBC 模式引入了初始化向量(IV),让每个分组都与前一个加密结果异或后再加密。这样做的好处是:相同的明文块会输出不同的密文块。我大部分项目都用 CBC,因为它安全且容易理解。
但要注意,CBC 不能并行加密,因为每个分组依赖前一个。另外,IV 必须是随机的,且每次加密都要更换。
CTR 模式:可并行的性能担当
CTR 模式是计数器模式。它用一个不断递增的计数器,和密钥一起生成密钥流,再用这个流与明文异或。CTR 可以并行计算,性能拉满。
我在高频交易系统里用过 CTR 模式,多核 CPU 同时算,速度非常快。但你要自己管理计数器,不能重复使用相同的计数-密钥组合。
| 模式 | 安全性 | 性能 | 是否可并行 | 适用场景 |
|---|---|---|---|---|
| ECB | 低 | 高 | 是 | 少数结构化数据 |
| CBC | 高 | 中 | 否 | 通用 |
| CTR | 高 | 高 | 是 | 高性能要求 |
我个人的建议是:如果你不确定用哪个模式,直接用 CBC 配合随机 IV。安全、稳定、不会出大问题。CTR 性能更好但需要更精心的设计。ECB 嘛,除了教科书很少会用到。