第二讲: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₀。

下面这张图把轮函数和密钥扩展的关系画清楚了——这是本章必须掌握的核心逻辑。

SM4轮函数与密钥扩展流程 SM4轮函数与密钥扩展关系图 128位主密钥MK 异或FK → K₀~K₃ 迭代32轮 T' + CKᵢ → rkᵢ 32个轮密钥rk₀~rk₃₁ 128位明文分组 拆成 X₀, X₁, X₂, X₃ 32轮迭代 每轮:T(X₁⊕X₂⊕X₃⊕rk) τ(S盒) + L(移位异或) 128位密文输出 关键交互 每轮迭代需要 对应的轮密钥 rkᵢ 解密时倒序使用 T函数内部 τ:4个并行的S盒 L:左移2/10/18/24异或

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性能审计,一定要盯着密钥扩展的代码实现——看它是不是纯查表和移位。

一句话总结本章核心:轮函数决定了加解密的基础延迟,密钥扩展决定了你的系统能否支撑高并发下的密钥切换。搞懂这两块,后面章节做具体审计方案时才能有的放矢。