4、SM4对称分组密码:从算法结构到工程落地

SM4,国密标准里的对称分组密码算法。说实话,它跟AES在思路上有相似之处,但细节上完全不同。我最早接触SM4是在一个金融数据加密项目里,当时甲方明确要求必须用国密,不能用AES替代。嗯,从那以后我就把SM4的每一层结构都啃透了。

这一章,我会把SM4的算法结构、工作模式、以及软硬件优化经验串起来讲。你想想看,搞懂这些,你才能在实际迁移中做到心里有底。

4.1 SM4算法结构:S盒、线性变换与密钥扩展

SM4是一个32轮迭代的分组密码,分组长度128位,密钥长度也是128位。结构上它采用非平衡Feistel网络——说白了,每一轮只处理32位,而不是像AES那样整块替换。

4.1.1 S盒:非线性替换

SM4只有一个8×8的S盒,固定且公开。我个人习惯把它记成一张256字节的查找表。S盒的设计基于仿射变换和逆映射,抵抗差分密码分析和线性密码分析的能力很强。

核心要点:SM4的S盒与AES的S盒没有直接关系。不要试图用AES的S盒替换SM4的S盒,否则算法安全性会崩塌。

我在项目中遇到过有人把SM4的S盒实现写成了动态计算,结果性能惨不忍睹。其实,直接查表是最快的,别折腾。

4.1.2 线性变换:扩散层

SM4的线性变换分为两种:一种用在轮函数里,叫L变换;另一种用在密钥扩展里,叫L'变换。L变换是一个32位输入的线性置换,本质上是循环移位和异或的组合。

公式看起来有点吓人,但说白了就是:

L(B) = B ⊕ (B <<< 2) ⊕ (B <<< 10) ⊕ (B <<< 18) ⊕ (B <<< 24)

L'变换稍微简单一点:

L'(B) = B ⊕ (B <<< 13) ⊕ (B <<< 23)

你想想看,这些移位量为什么选2、10、18、24?我研究过,这些参数能保证在4轮之内达到完全扩散。嗯,设计者考虑得很周全。

4.1.3 密钥扩展:从128位主密钥生成32轮子密钥

SM4的密钥扩展过程用到了系统参数FK和固定参数CK。FK有4个,CK有32个,都是公开常数。

流程是这样的:

  1. 把128位主密钥分成4个32位字:(MK0, MK1, MK2, MK3)
  2. 与FK异或得到初始状态:(K0, K1, K2, K3)
  3. 从i=0到31,迭代计算:Ki+4 = Ki ⊕ L'(T(Ki+1 ⊕ Ki+2 ⊕ Ki+3 ⊕ CKi))
  4. 子密钥 rki = Ki+4

我的经验:密钥扩展可以预计算。在加密大量数据时,提前算好32轮子密钥存起来,能省掉每次加密时的重复计算。我曾经在一个嵌入式项目里这么干,性能提升了大约15%。

4.2 SM4的工作模式:ECB、CBC、CTR、GCM

SM4本身只定义了分组加密的核,怎么处理多块数据,要靠工作模式。国密标准里明确支持ECB、CBC、CTR和GCM。我按实际使用频率排序讲。

模式 是否需要IV 并行加/解密 认证 推荐场景
ECB 否 均可 无 不推荐,除非单块数据
CBC 是 仅解密 无 文件加密、传统系统迁移
CTR 是(Nonce+Counter) 均可 无 流式数据、高性能场景
GCM 是(Nonce) 均可 有(GMAC) 网络通信、TLS、需要认证的场景

4.2.1 ECB模式——能不用就别用

ECB模式把每块数据独立加密。同样的明文块会得到同样的密文块。这在很多场景下会泄露数据模式。我曾经审计过一个旧系统,对方用ECB加密用户头像,结果头像轮廓清晰可见——嗯,这显然不行。

警告:除非你只加密一个分组(比如加密一个密钥),否则不要用ECB。国密标准虽然支持,但工程上我建议直接禁用。

4.2.2 CBC模式——最常用的传统模式

CBC模式需要初始化向量IV,每个明文块先与前一个密文块异或,再加密。解密可以并行,但加密必须串行。

我建议:IV必须随机生成,且每次加密都要不同。IV不需要保密,但必须保证唯一性。我在项目中见过有人把IV设成固定值,结果密文模式被破解——这是典型的坑。

4.2.3 CTR模式——高性能首选

CTR模式把计数器加密后与明文异或。加密和解密完全一样,而且可以并行。说白了,它把分组密码变成了流密码。

CTR模式的关键是计数器管理。Nonce+Counter的组合必须唯一。我习惯用12字节Nonce加4字节Counter,这样单次加密最多处理2^32个分组,够用了。

4.2.4 GCM模式——带认证的现代模式

GCM模式在CTR基础上增加了GMAC认证标签。它既能加密,又能防篡改。国密标准里SM4-GCM是推荐的组合,尤其适合网络协议。

GCM的坑主要在Nonce管理。Nonce长度建议用12字节,这是最优化实现。如果Nonce长度不是12字节,内部要做GHASH计算,性能会下降。我记得有一次优化一个网关项目,把Nonce从16字节改成12字节,吞吐量直接提升了20%。

4.3 SM4的软件与硬件实现优化

SM4在不同平台上跑,优化策略完全不同。我分别讲软件和硬件两个方向。

4.3.1 软件优化:查表、SIMD与指令集

软件优化的核心是减少计算量、利用CPU特性。

  • S盒查表:把S盒做成256字节的数组,直接查。别用计算方式实现S盒,那会慢一个数量级。
  • 循环展开:SM4有32轮,手动展开成32段代码,减少循环开销。我在一个x86项目里试过,展开后快了约30%。
  • SIMD指令:用SSE/AVX或NEON一次处理多个分组。CTR和GCM模式天然适合SIMD,因为可以并行生成密钥流。我建议优先优化CTR模式,收益最明显。
  • 查表合并:把S盒和线性变换合并成4张32位大表(T表),每张表1024字节。这样一轮加密只需要4次查表和4次异或。嗯,这是SM4软件优化的经典手法。

避坑指南:我曾经在ARM Cortex-M4上做SM4优化,发现查表法虽然快,但S盒表放在Flash里比放在RAM里慢很多。后来我把S盒表拷贝到RAM里,性能提升了40%。不同平台的内存访问延迟差异很大,一定要实测。

4.3.2 硬件优化:流水线、并行与面积权衡

硬件实现SM4,主要考虑吞吐量和芯片面积。

  • 全流水线架构:32轮用32级流水线,每轮一个时钟周期。这样每时钟周期都能输出一个加密结果。吞吐量极高,但面积也大。
  • 迭代架构:只实现一轮的硬件,32个时钟周期完成一次加密。面积小,适合资源受限的芯片。
  • S盒硬件实现:用组合逻辑实现S盒,而不是查ROM。组合逻辑的延迟更低,适合高频设计。我见过一个ASIC项目,组合逻辑S盒跑到了1GHz以上。
  • 密钥扩展硬件化:密钥扩展可以做成独立的硬件模块,与加密流水线并行工作。这样在切换密钥时没有等待周期。

我个人建议:如果芯片面积允许,优先用全流水线架构。SM4的32轮流水线比AES的10轮流水线更规整,实现起来反而简单。

4.4 SM4算法结构总览图

下面这张SVG图把SM4的核心结构串起来了。从主密钥扩展出32轮子密钥,然后每轮经过S盒、线性变换,最后输出密文。你一看就明白。

SM4算法结构总览 128位主密钥 MK 密钥扩展 (FK + CK) 32轮子密钥 rk[0] ... rk[31] 每轮32位 128位明文 P 32轮迭代 S盒 → L变换 → 异或 每轮使用 rk[i] 128位密文 C 说明 • 非平衡Feistel结构 • 分组128位,密钥128位 • 32轮迭代,每轮32位

4.5 模式选择与迁移建议

如果你正在做从其他算法到SM4的迁移,我建议按这个优先级选模式:

  1. 新系统:优先用SM4-GCM。带认证,安全性高,性能也不错。
  2. 从AES-GCM迁移:直接替换成SM4-GCM,接口几乎一样。注意Nonce长度统一成12字节。
  3. 从AES-CBC迁移:替换成SM4-CBC,IV生成逻辑不变。但建议后续升级到GCM。
  4. 高性能场景:用SM4-CTR,配合SIMD优化,吞吐量可以接近硬件加密。

重要提醒:SM4的密钥长度固定128位,没有192或256位版本。迁移时不要试图扩展密钥长度,那会破坏算法标准。如果觉得128位不够安全,可以考虑用SM2做密钥封装,或者结合SM3做混合方案。