3. SM4 加解密流程:别被对称性骗了
好,咱们直接切入正题。SM4 的加解密流程,说白了就是一套精心设计的“数据搅拌机”。我刚开始接触它的时候,觉得那些轮函数、S 盒什么的特别唬人。后来调了几个月的吞吐量才发现,真正理解它的执行路径,才是优化的起点。
3.1 加密过程:32 轮“拧麻花”
SM4 加密的核心,是一个 32 轮的迭代结构。你想想看,128 位的数据块进来,要经过 32 次同样的“揉搓”才能变成密文。每一次轮操作,都包含三个关键动作:异或、非线性变换、线性移位。
我个人习惯把这 32 轮比作“拧麻花”。每一轮,当前轮的 32 位数据和轮密钥做异或,然后扔进一个叫 S 盒的“迷宫”里做替换,最后再经过一个线性变换把数据彻底打乱。
具体来说,第 i 轮的处理公式是这样的:
X_{i+4} = X_i ⊕ T( X_{i+1} ⊕ X_{i+2} ⊕ X_{i+3} ⊕ rk_i )
其中:
- X_i 到 X_{i+3} 是当前轮的四个 32 位输入
- rk_i 是第 i 轮的轮密钥
- T(·) 是合成置换,它由非线性变换 τ 和线性变换 L 组成
对了,这里面的合成置换 T 是性能关键。我记得第一次实现这个功能时,在 S 盒替换上栽过跟头。S 盒本质上是一个 8x8 的查找表,输入 8 位输出 8 位。但如果你用软件实现,每次查表都有一次内存访问。吞吐量一大,这个开销就暴露了。
3.2 解密的对称性:真的是“反过来”吗?
很多人会说:“SM4 解密就是加密的逆过程呗,把密钥倒过来用就行了。” 这话说对了一半。解密和加密的轮函数结构完全一样,但轮密钥的使用顺序是相反的。
加密时我们用 rk_0 到 rk_31,解密时就用 rk_31 到 rk_0。但这里有一个微妙的点——大多数人忽略了线性变换 L 也要反过来。加密时用的 L 变换是 32 位循环左移和异或组合,解密时必须使用它的逆变换 L'。
我在产品落地时就踩过这个坑。代码里图省事,直接把轮密钥顺序反了一下,结果加解密结果怎么都对不上。后来查了国标文档才意识到,L 和 L' 是两个不同的变换矩阵。
说白了,SM4 的解密之所以能复用加密的硬件/软件模块,靠的是这种“结构性对称”,而不是简单的“把密钥倒着喂”。你想想看,如果只是倒序喂密钥,那线性层的问题怎么解决?
3.3 工作模式:ECB、CBC、CTR 怎么选?
SM4 本身只定义了 128 位数据块怎么加密。但是实际应用中,你要加密的数据远比 128 位长。这时候就需要工作模式来定义“怎么处理多个数据块”。
我见过不少新人上来就选 ECB,因为代码最简单。但我想说:除非你的数据是随机密钥材料或者没有结构性信息,否则千万别在线上环境用 ECB。为什么呢?因为相同的明文块会产生相同的密文块,这在图像、结构化数据里简直就是灾难。
3.4 模式对比一览
下面这个表格是我自己整理的,每次做技术选型都会拿出来看一眼。三种模式各有各的脾气。
| 模式 | 核心逻辑 | 并行度 | 错误传播 | 我推荐的使用场景 |
|---|---|---|---|---|
| ECB | 每个块独立加密 | 最高(完全并行) | 错误仅影响该块 | 密钥分发、小数据量测试 |
| CBC | 当前块与上一块密文异或后加密 | 无(必须串行) | 一个错误会影响当前块和下一块 | 文件加密、通用数据传输 |
| CTR | 用递增计数器产生密钥流,再与明文异或 | 极高(可预计算密钥流) | 错误不影响其他块 | 高吞吐量服务器、网络流加密 |
3.5 吞吐量调优的“第一块砖”
讲完了流程,我得聊聊这些模式怎么影响吞吐量。ECB 和 CTR 天生适合并行,我建议你在多核 CPU 或者 GPU 上优先考虑它们。CBC 模式呢?你想想看,每一个块都要等前一个块算完,流水线根本跑不起来。我曾经在一个 32 核的平台上做测试,CBC 的吞吐量只有 CTR 的 60% 左右。
另外,CTR 模式还有一个隐藏优势:加解密是同一种操作。加密是“密钥流 XOR 明文”,解密是“密钥流 XOR 密文”。这意味着你可以只实现一个函数,通过这个模式同时做加解密。我个人习惯在项目中把 SM4-CTR 作为首选,除非业务强制要求 CBC。
3.6 小结一下
嗯,这一章内容不少。SM4 加密的 32 轮迭代是基本功,解密的对称性不只是密钥倒序,工作模式直接决定了你能把硬件压榨到什么程度。我见过不少项目在选型阶段忽略了模式的影响,等到联调时发现吞吐量上不去,回头改模式,那代价可就大了。所以,开工之前,把这几个点想清楚,后面对你只有好处。记住一句话:理解了流程,才谈得上调优。