3. SM4 算法原理:Feistel 结构、轮函数、密钥扩展、S 盒设计
SM4 算法,说白了就是咱们国家自己的分组密码标准。我在存储加密项目里跟它打交道最多——它不像 SM2 那样需要复杂的椭圆曲线运算,也不像 SM3 那样只做摘要。SM4 的核心就是“对称加密”,加密解密用同一把钥匙。
嗯,咱们直接切入正题。SM4 的结构其实不复杂,但有几个关键点你必须吃透:Feistel 结构、轮函数、密钥扩展,还有那个神秘的 S 盒。我一个个讲,顺便聊聊我在实际落地时踩过的坑。
3.1 Feistel 结构:对称加密的经典骨架
SM4 用的是典型的 Feistel 网络。你想想看,为什么 Feistel 结构这么流行?因为它有一个巨大的好处:加密和解密的逻辑几乎一样,只是密钥的使用顺序反过来。这在硬件实现上能省不少事。
SM4 的分组长度是 128 位,密钥长度也是 128 位。它把 128 位数据分成左右两半,每半 32 位。然后经过 32 轮迭代,最后再合并。我画了一张图,你看一眼就明白了:
你看,每一轮只处理一半的数据,另一半直接原样传递。这样做的好处是——解密时只要把轮密钥倒过来用就行。我在做 SSD 主控的加密模块时,就是利用这个特性,把加密和解密电路复用了,省了不少门电路。
3.2 轮函数:SM4 的核心战斗力
轮函数 F 是 SM4 的精髓。它接收 32 位的右半部分数据和 32 位的轮密钥,输出 32 位的结果。这个 F 函数内部又分三步:异或 → S 盒替换 → 线性变换。
公式长这样:
F(X, rk) = L( τ( X ⊕ rk ) )
其中 τ 是 S 盒替换,L 是线性变换。我拆开来讲。
3.2.1 合成置换 τ
τ 操作其实很简单:把 32 位输入分成 4 个字节,每个字节查一次 S 盒,得到 4 个新字节,再拼回去。说白了就是 4 个并行的 S 盒查找。
嗯,这里要注意:SM4 的 S 盒是 固定的 16×16 查找表,输入 8 位,输出 8 位。我待会儿专门讲 S 盒的设计。
3.2.2 线性变换 L
τ 的输出还要经过 L 变换。L 是一个 32 位的线性函数,公式如下:
L(B) = B ⊕ (B <<< 2) ⊕ (B <<< 10) ⊕ (B <<< 18) ⊕ (B <<< 24)
这里的 <<< 表示循环左移。你看,就是 4 次移位再异或。这个设计的目的,是让 一个比特的变化能快速扩散到整个 32 位。我在项目中测试过,SM4 的扩散速度比 DES 快得多,3 轮就能达到完全雪崩效应。
3.3 密钥扩展算法:从 128 位主密钥到 32 个轮密钥
SM4 的密钥扩展比 AES 简单不少。主密钥 MK 是 128 位,分成 4 个 32 位字:(MK0, MK1, MK2, MK3)。
首先,用固定参数 FK 对 MK 做一次异或,得到初始状态 K:
(K0, K1, K2, K3) = (MK0⊕FK0, MK1⊕FK1, MK2⊕FK2, MK3⊕FK3)
FK 是系统参数,固定值,我就不列了,你查标准文档就有。
然后,从 i=0 到 31,生成 32 个轮密钥 rki:
rki = Ki+4 = Ki ⊕ τ'(Ki+1 ⊕ Ki+2 ⊕ Ki+3 ⊕ CKi)
这里的 τ' 和轮函数里的 τ 几乎一样,唯一的区别是:τ' 后面的线性变换 L' 不同。
| 变换 | 用途 | 线性变换公式 |
|---|---|---|
| L | 轮函数中的线性变换 | B ⊕ (B<<<2) ⊕ (B<<<10) ⊕ (B<<<18) ⊕ (B<<<24) |
| L' | 密钥扩展中的线性变换 | B ⊕ (B<<<13) ⊕ (B<<<23) |
你看,L' 比 L 简单,只用了 2 次移位。为什么?因为密钥扩展不需要那么强的扩散性,够用就行。CKi 是固定常数,每个轮次不同,用来打破对称性。
3.4 S 盒设计:SM4 的非线性灵魂
S 盒是 SM4 唯一的非线性部件。没有它,整个算法就是线性的,一攻就破。SM4 的 S 盒是一个 16×16 的查找表,输入 8 位,输出 8 位。
我直接给你 S 盒的数值:
| 行\列 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | D6 | 90 | E9 | FE | CC | E1 | 3D | B7 | 16 | B6 | 14 | C2 | 28 | FB | 2C | 05 |
| 1 | 2B | 67 | 9A | 76 | 2A | BE | 04 | C3 | AA | 44 | 13 | 26 | 49 | 86 | 06 | 99 |
| 2 | 9C | 42 | 50 | F4 | 91 | EF | 98 | 7A | 33 | 54 | 0B | 43 | ED | CF | AC | 62 |
| 3 | E4 | B3 | 1C | A9 | C9 | 08 | E8 | 95 | 80 | DF | 94 | FA | 75 | 8F | 3F | A6 |
| 4 | 47 | 07 | A7 | FC | F3 | 73 | 17 | BA | 83 | 59 | 3C | 19 | E6 | 85 | 4F | A8 |
| 5 | 68 | 6B | 81 | B2 | 71 | 64 | DA | 8B | F8 | EB | 0F | 4B | 70 | 56 | 9D | 35 |
| 6 | 1E | 24 | 0E | 5E | 63 | 58 | D1 | A2 | 25 | 22 | 7C | 3B | 01 | 21 | 78 | 87 |
| 7 | D4 | 00 | 46 | 57 | 9F | D3 | 27 | 52 | 4C | 36 | 02 | E7 | A0 | C4 | C8 | 9E |
| 8 | EA | BF | 8A | D2 | 40 | C7 | 38 | B5 | A3 | F7 | F2 | CE | F9 | 61 | 15 | A1 |
| 9 | E0 | AE | 5D | A4 | 9B | 34 | 1A | 55 | AD | 93 | 32 | 30 | F5 | 8C | B1 | E3 |
| A | 1D | F6 | E2 | 2E | 82 | 66 | CA | 60 | C0 | 29 | 23 | AB | 0D | 53 | 4E | 6F |
| B | D5 | DB | 37 | 45 | DE | FD | 8E | 2F | 03 | FF | 6A | 72 | 6D | 6C | 5B | 51 |
| C | 8D | 1B | AF | 92 | BB | DD | BC | 7F | 11 | D9 | 5C | 41 | 1F | 10 | 5A | D8 |
| D | 0A | C1 | 31 | 88 | A5 | CD | 7B | BD | 2D | 74 | D0 | 12 | B8 | E5 | B4 | B0 |
| E | 89 | 69 | 97 | 4A | 0C | 96 | 77 | 7E | 65 | B9 | F1 | 09 | C5 | 6E | C6 | 84 |
| F | 18 | F0 | 7D | EC | 3A | DC | 4D | 20 | 79 | EE | 5F | 3E | D7 | CB | 39 | 48 |
查表方法:输入字节的高 4 位是行号,低 4 位是列号。比如输入 0x3A,查第 3 行第 A 列,得到 0xFA。
SM4 的 S 盒设计基于 有限域 GF(2⁸) 上的仿射变换和乘法逆元。说白了,就是先求乘法逆,再做一次仿射变换。这个设计思路和 AES 的 S 盒很像,但参数不同。
- 非线性: 乘法逆元在 GF(2⁸) 上是最优的非线性变换,能抵抗线性密码分析。
- 差分均匀性: SM4 的 S 盒差分均匀性为 2⁻⁶,意味着差分攻击的复杂度高达 2⁶⁴ 以上。
- 代数次数: S 盒的代数次数为 7,能抵抗插值攻击。
我个人习惯在代码里用查表法实现 S 盒,256 字节的数组,查起来快。但在 FPGA 里,我建议用组合逻辑实现——把 S 盒的布尔表达式化简,能省不少 LUT。
3.5 完整加密流程一览
把上面所有部件串起来,SM4 的加密流程就是:
- 初始化: 128 位明文分成 4 个 32 位字 (X0, X1, X2, X3)。
- 32 轮迭代: 对 i=0 到 31,计算 Xi+4 = Xi ⊕ F(Xi+1, rki)。
- 反序输出: 密文为 (X35, X34, X33, X32)。
解密就是反过来:用 rk31 到 rk0 的顺序做同样的 32 轮迭代。