3. SM4分组密码算法:从结构到实战

SM4,国密标准里的分组密码算法。说实话,我第一次接触它的时候,第一反应是——这不就是类AES的东西吗?后来深入项目才发现,它虽然也是Feistel结构,但细节上跟DES、AES差别挺大的。今天咱们就把SM4的里里外外捋一遍。

3.1 SM4算法结构——Feistel网络的“中国版”

SM4的分组长度是128位,密钥长度也是128位。它用的是Feistel网络结构,每轮只加密一半数据,另一半直接拿过来用。我刚开始做固件移植时,觉得这种结构有点“浪费”——为什么不让所有位同时参与运算?后来才明白,Feistel结构的最大好处是加解密几乎一样,硬件实现省资源。

SM4总共32轮迭代。每轮的结构很清晰:

  • 输入:128位数据分成4个32位字 (Xi, Xi+1, Xi+2, Xi+3)
  • 轮函数F:对Xi+1、Xi+2、Xi+3和轮密钥rki做运算
  • 输出:新字 Xi+4 = F(Xi+1, Xi+2, Xi+3, rki) ⊕ Xi

轮函数F内部又分三步:合成置换T、非线性变换τ、线性变换L。说白了,就是先做S盒替换,再做移位异或。我在一个IoT项目里用软件实现SM4时,发现S盒查表是性能瓶颈,后来改成预计算+内存对齐,速度提了将近一倍。

核心要点:SM4的Feistel结构每轮只更新1/4的数据(32位),但通过32轮迭代,每个输出位都受到所有输入位和密钥位的影响。这就是所谓的“雪崩效应”。

3.2 密钥扩展——从16字节到32个轮密钥

SM4的密钥扩展算法跟加解密流程很像,用的也是Feistel结构。初始密钥MK是128位,分成4个32位字:MK0、MK1、MK2、MK3。

扩展过程分两步:

  1. 系统参数FK:用固定常量FK0~FK3对初始密钥做异或,得到K0~K3
  2. 迭代生成:对i从0到31,rki = Ki+4 = Ki ⊕ T'(Ki+1 ⊕ Ki+2 ⊕ Ki+3 ⊕ CKi)

这里的T'跟加密轮函数里的T类似,只是线性变换部分换成了L'。CKi是固定常数。我记得第一次看密钥扩展代码时,差点把CK和FK搞混——FK是初始用的,CK是每轮用的,千万别弄反。

避坑指南:我曾经在移植SM4到ARM Cortex-M0时,发现密钥扩展结果不对。查了半天,原来是CK常量的字节序问题。SM4的CK是按大端定义的,但我的平台默认小端。记住:国标文档里的常量都是大端,移植时一定要确认字节序。

3.3 加解密流程——对称之美

SM4的加密和解密流程完全对称,只是轮密钥的使用顺序相反。加密时用rk0到rk31,解密时用rk31到rk0。这就是Feistel结构最优雅的地方——加解密可以用同一套硬件。

加密流程:

  • 输入128位明文,分成4个32位字
  • 执行32轮迭代,每轮用轮函数F和对应轮密钥
  • 最后一轮输出后,做反序变换得到密文

解密流程:

  • 输入128位密文,分成4个32位字
  • 执行32轮迭代,轮密钥顺序与加密相反
  • 最后一轮输出后,做反序变换得到明文

下面这张图把SM4的整体结构画清楚了:

SM4算法整体结构图 128位明文 X0 X1 X2 X3 32轮迭代 (i = 0, 1, ..., 31) 轮函数 F (含τ变换+L变换) 轮密钥 rk_i ⊕ X_{i+4} 反序变换 (X32~X35) 128位密文 密钥扩展 MK → K0~K3 → rk0~rk31 (32轮)

3.4 SM4的工作模式——ECB/CBC/CTR/GCM

SM4本身只是个分组密码,怎么用还得看工作模式。国密标准里推荐了四种模式,我按实际项目中的使用频率排个序:

模式 特点 适用场景 我的评价
ECB 每个分组独立加密,简单快速 密钥分发、少量数据 别用在长数据上,模式太明显
CBC 需要IV,前后分组关联 文件加密、固件包加密 最常用,但注意IV不能重复
CTR 流密码模式,可并行 高速传输、流式数据 我喜欢这个模式,性能好
GCM 认证加密,同时提供完整性 网络传输、安全通信 最安全,但实现复杂一点

ECB模式:最简单,但也是最危险的。同样的明文块会生成同样的密文块。我在一个固件升级项目里见过有人用ECB加密固件镜像,结果固件里有大量重复的0xFF填充,密文一眼就能看出规律。嗯,后来我建议他换了CBC。

CBC模式:需要初始化向量IV。加密时每个明文块先跟上一个密文块异或,再加密。解密时反过来。注意:IV必须是随机的,而且每次加密都要换。我曾经见过一个产品把IV写死在代码里,结果跟ECB一样不安全。

CTR模式:把SM4当成流密码用。加密计数器,然后跟明文异或。好处是可以并行计算,而且加解密用同一套逻辑。我在做高速数据采集卡时用的就是CTR模式,性能比CBC快了将近3倍。

GCM模式:CTR模式加上GMAC认证标签。既能加密又能防篡改。你想想看,如果只加密不认证,攻击者可以篡改密文,解密后得到乱码,但接收方不知道数据被改过。GCM就是解决这个问题的。

重要提醒:GCM模式中,nonce(IV)绝对绝对不能重复使用。一旦重复,攻击者可以恢复出认证密钥H,整个系统就废了。我在做安全审计时,发现过因为随机数生成器种子固定导致nonce重复的案例——后果很严重。

3.5 SM4在数据加密传输中的应用

说完了理论,咱们看看实际怎么用。我在一个物联网网关项目里,用SM4-CBC加密设备上报的传感器数据。流程大概是这样的:

  1. 密钥协商:设备和服务端通过SM2密钥交换,协商出SM4的会话密钥
  2. 数据打包:传感器数据加上时间戳、设备ID,组成明文
  3. SM4加密:用CBC模式加密,IV由设备生成并附在密文前面
  4. 传输:加密后的数据通过MQTT发送到服务端
  5. 解密验证:服务端用协商好的密钥解密,验证时间戳防重放

这里有个细节:IV怎么传?我习惯把IV直接放在密文前面,因为IV不需要保密,但必须保证完整性。如果担心IV被篡改,可以用GCM模式,认证标签会保护IV。

实战建议:在嵌入式设备上实现SM4时,注意以下几点:

  • S盒查表用const数组,放在Flash里,别放RAM
  • 32轮迭代可以展开,减少循环开销
  • 如果CPU有硬件加密引擎,优先用硬件——我测过,软件实现SM4-CBC在100MHz MCU上大概能跑50Mbps,硬件能到200Mbps以上

最后说一句:SM4的国标文档(GB/T 32907-2016)写得挺清楚的,但有些细节需要自己踩坑才能理解。比如密钥扩展里的CK常量,文档里给的是十六进制表,但实际代码里要按32位字来取。我第一次实现时,对着文档一行一行对,才发现这个坑。