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。
扩展过程分两步:
- 系统参数FK:用固定常量FK0~FK3对初始密钥做异或,得到K0~K3
- 迭代生成:对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的整体结构画清楚了:
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加密设备上报的传感器数据。流程大概是这样的:
- 密钥协商:设备和服务端通过SM2密钥交换,协商出SM4的会话密钥
- 数据打包:传感器数据加上时间戳、设备ID,组成明文
- SM4加密:用CBC模式加密,IV由设备生成并附在密文前面
- 传输:加密后的数据通过MQTT发送到服务端
- 解密验证:服务端用协商好的密钥解密,验证时间戳防重放
这里有个细节: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位字来取。我第一次实现时,对着文档一行一行对,才发现这个坑。