第二讲:SM4算法原理——轮函数与密钥扩展
SM4算法,说白了就是一套“怎么把明文变成密文”的固定打法。你想想看,国密标准里那么多算法,为什么我们要单独拎出轮函数和密钥扩展来讲?
因为我做性能压测时吃过亏。最早我照着网上抄的SM4代码直接跑并发,结果密钥扩展那步占了将近40%的CPU时间。后来我才明白——不懂轮函数,你就不知道瓶颈在哪;不懂密钥扩展,你连加解密次数都算不准。
2.1 整体结构一览
SM4是个分组密码,分组长度128位,密钥长度也是128位。它的核心是32轮迭代,每一轮干的事差不多,但每轮用的“轮密钥”不一样。这些轮密钥就是从主密钥里“变”出来的。
关键指标:
- 分组长度:128 bits(16字节)
- 密钥长度:128 bits
- 轮数:32
- 轮密钥长度:32 bits,共32个
- 算法结构:Feistel网络变形(非平衡Feistel)
我个人习惯把SM4想象成一个流水线车间:
原料(明文)进来,经过32道工序(轮函数),每道工序换个模具(轮密钥),最后出来的就是成品(密文)。
2.2 轮函数(Round Function)
每一轮干了什么?我来拆开说。
SM4的轮函数输入是四个32位的字:(X₀, X₁, X₂, X₃)。输出是一个32位的新字X₄。然后整个状态左移一位,凑成下一轮的(X₁, X₂, X₃, X₄)。
公式长这样:
X₄ = F(X₀, X₁, X₂, X₃, rk) = X₀ ⊕ T(X₁ ⊕ X₂ ⊕ X₃ ⊕ rk)
这里面唯一有技术含量的就是那个T()函数。T()由两部分组成:
2.2.1 非线性变换 τ
τ说白了就是并行的4个S盒替换。每个S盒输入8 bits,输出8 bits。S盒是固定的查找表(你可以当成一个硬编码的数组)。
我在项目中遇到过一个问题:用软件实现时,S盒查表如果没对齐到256字节边界,性能会掉15%以上。嗯,做并发审计时,这个细节能让你多挤出几千TPS。
2.2.2 线性变换 L
L是个循环移位加异或的组合,公式如下:
L(B) = B ⊕ (B ≪ 2) ⊕ (B ≪ 10) ⊕ (B ≪ 18) ⊕ (B ≪ 24)
注意:这里面全是32位循环左移。说白了就是让1 bit的变化能快速扩散到整个32位字里。
避坑指南:我曾经在嵌入式平台上用32位MCU实现SM4,循环移位操作如果用C语言的左移+或来模拟,编译器可能优化不好。建议直接用内建函数或汇编,不然密钥扩展那部分的移位会让人疯掉。
2.3 密钥扩展(Key Expansion)
密钥扩展负责把128位主密钥变成32个32位的轮密钥。这个过程的门道比轮函数还多。
2.3.1 初始变换
主密钥MK = (MK₀, MK₁, MK₂, MK₃),每个32位。先跟四个系统参数FK异或:
K₀ = MK₀ ⊕ FK₀
K₁ = MK₁ ⊕ FK₁
K₂ = MK₂ ⊕ FK₂
K₃ = MK₃ ⊕ FK₃
FK值固定,我直接列出来:
| 参数 | 值(十六进制) |
|---|---|
| FK₀ | A3B1BAC6 |
| FK₁ | 56AA3350 |
| FK₂ | 677D9197 |
| FK₃ | B27022DC |
2.3.2 迭代生成
从i=0到31:
rkᵢ = Kᵢ₊₄ = Kᵢ ⊕ T'(Kᵢ₊₁ ⊕ Kᵢ₊₂ ⊕ Kᵢ₊₃ ⊕ CKᵢ)
注意这里T'()和轮函数里的T()不一样——T'的线性部分换成了:
L'(B) = B ⊕ (B ≪ 13) ⊕ (B ≪ 23)
说白了密钥扩展的扩散性比加密更保守一些,防止轮密钥之间产生弱关联。
CKᵢ是32个固定常数,由特定公式生成。你不需要背,但要知道它们都是32位的值,用来打散规律。
常见误解:有人以为密钥扩展只能算一次,然后32个轮密钥存着就行。对,桌面端没问题。但在云原生环境下,每个连接可能独立握手,频繁切换密钥——密钥扩展的耗时就会变成显性瓶颈。我在做SM4并发审计时,专门把密钥扩展做成了预计算缓存,一下子压测曲线就平了。
2.4 整轮流程梳理
把加密和解密串起来看,流程其实一样,只是轮密钥的使用顺序反过来。加密用rk₀, rk₁, ..., rk₃₁;解密用rk₃₁, rk₃₀, ..., rk₀。
下面这张图把轮函数和密钥扩展的关系画清楚了——这是本章必须掌握的核心逻辑。
2.5 解密与加密的对称性
SM4的解密过程和加密完全相同,只是轮密钥倒着用。你想想看,这能省多少事?做FPGA实现时,加解密模块可以复用一套逻辑,只需要把轮密钥数组正着给或者反着给。
我用一个简单的对比表来总结:
| 项目 | 加密 | 解密 |
|---|---|---|
| 轮密钥顺序 | rk₀ → rk₁ → … → rk₃₁ | rk₃₁ → rk₃₀ → … → rk₀ |
| 轮函数结构 | F(X₀, X₁, X₂, X₃, rk) | 完全相同 |
| S盒 | 标准SM4 S盒 | 相同 |
| 线性变换L | 左移2/10/18/24 | 相同 |
| 密钥扩展算法 | 使用T' + CKᵢ | 相同,只需预计算一次 |
个人经验:做并发容量审计时,我习惯把密钥扩展单独做性能测量。因为很多SDK把密钥扩展和加密绑在一起,你一次加解密看不出问题,但并发上去就露馅了。单独测密钥扩展的吞吐量,能帮你精准定位瓶颈。
2.6 性能审计关注点
好了,原理讲完,我们得想想——这些原理对性能审计有什么实际影响?
- S盒查表延迟:每个S盒查表需要一次内存访问,32轮×4个S盒=128次查表。如果S盒不在CPU缓存里,性能直接崩。
- 移位与异或:L变换和L'变换都是简单的位运算,CPU处理起来极快,基本不构成瓶颈。
- 密钥扩展计算量:密钥扩展约等于1/3轮函数的计算量。如果你的系统频繁更换密钥(比如每条消息都重新握手),密钥扩展的占比会飙升。
- 并行化潜力:SM4每轮依赖上一轮的结果,无法并行。但密钥扩展和加密可以流水线化——提前算好下一轮需要的轮密钥。
我曾经掉过的坑:有次做压测,发现加解密性能随密钥大小波动。查了三天才发现,是密钥扩展里那个CK常数生成用了除法和取模,编译器没优化。后来改成查表预计算,性能直接翻倍。所以搞SM4性能审计,一定要盯着密钥扩展的代码实现——看它是不是纯查表和移位。
一句话总结本章核心:轮函数决定了加解密的基础延迟,密钥扩展决定了你的系统能否支撑高并发下的密钥切换。搞懂这两块,后面章节做具体审计方案时才能有的放矢。