第1章 · SM4算法概述与国家标准归纳总结
做密码学这么多年,每次接手国密项目,第一件事就是把SM4的参数配置拿出来重新对一遍。为啥?因为这东西看起来简单,但坑多。SM4是国家密码管理局2006年发布的对称分组密码标准,全称《GM/T 0002-2012 SM4分组密码算法》。说白了你只需要记住三点:它是国标、它是分组密码、它是用来加密数据的。
1.1 什么是SM4算法
SM4算法,以前叫SMS4,是咱们国家自主设计的对称加密算法。我在2016年第一次接触它的时候,心里还犯嘀咕:跟AES比到底差多少?后来调了几个项目才发现——SM4在软件实现上其实不慢,硬件实现更稳。关键它是国家标准,国内很多合规场景必须用它。
简单讲,SM4的分组长度是128比特,密钥长度也是128比特。它属于Feistel结构,迭代32轮。没接触过Feistel?没关系,你就把它想象成一个分左右两半的绞肉机,经过32次来回挤压,明文就变成密文了。解密过程几乎一样,只是轮密钥的顺序反过来。
核心参数一览
- 分组长度:128比特(即16字节)
- 密钥长度:128比特(即16字节)
- 算法结构:Feistel网络,32轮迭代
- 模式支持:ECB、CBC、CFB、OFB、CTR等(标准中推荐CBC和CTR)
- S盒:固定8×8 S盒,共256个元素
1.2 国家标准体系与演进
SM4最早出现在《GM/T 0002-2012》里。2016年之后,国家密码管理局把它升级到了GB/T 32907-2016,变成了国标。等于说原来只是行业标准,后来成了整个国家的推荐标准。嗯,这里要注意:很多商用密码产品要求必须支持GB/T 32907版本。
我在一次等保测评项目中遇到过这种情况——客户用了某厂商的SM4加密卡,结果评测时发现它只实现了GM/T 0002-2012的老版本,有些API参数不兼容新国标。虽然算法本质一样,但接口规范有差异,最后折腾了两周才搞定。所以我在课程里一直强调:版本要看清。
| 标准编号 | 发布年份 | 主要内容 | 状态 |
|---|---|---|---|
| GM/T 0002-2012 | 2012 | SM4分组密码算法定义 | 行业标准(部分被替代) |
| GB/T 32907-2016 | 2016 | SM4分组密码算法(国标版) | 现行推荐标准 |
| GM/T 0042-2016 | 2016 | SM4算法的CBC/CTR模式规范 | 现行 |
| GB/T 38624-2020 | 2020 | SM4在物联网场景下的应用指南 | 现行 |
你想想看,一个算法如果只是密码算法本身,你写代码时只需要把加密函数调通就行了。但实际落地上不同标准的差异主要体现在模式选择、填充方式、初始向量(IV)长度这些细节上。比如GB/T 32907-2016明确要求IV必须是128比特的随机数,而早期的某些行业文档对IV没有强制规定。
1.3 算法基本流程速览
我习惯把SM4的加密过程分成三步走:密钥扩展 → 轮函数迭代 → 反序变换。别看它32轮,其实每轮做的事情都一样——左半部分和本轮子密钥喂进F函数,输出跟右半部分异或,然后左右交换。是不是很简单?
- 密钥扩展:给定16字节密钥,通过固定系统参数FK和CK生成32个32位的轮密钥rk[0] ~ rk[31]。
- 32轮迭代加密:将128位明文分成4个32位字(X0, X1, X2, X3),每轮用F函数更新X3。
- 反序变换:最后对输出做反序处理,得到密文。
注意:SM4的S盒是固定的,这一点跟AES完全不同。你不需要自己生成S盒,直接用标准给出的256字节表。我见过有人好奇写了个S盒生成器,结果加密解密的兼容性全崩了——千万别自行“优化”S盒。
1.4 加密模式与参数配置
说实话,很多人以为SM4就是调一下库,传个密钥和明文就完事了。真正踩坑的是模式选择和IV配置。我个人项目里最喜欢用CBC模式,因为它能掩盖明文的重复模式。但CTR模式在并行处理场景下更有优势。
我的建议:如果做文件加密,用CBC模式加上PKCS7填充;如果做网络数据流加密,用CTR模式不需要填充,而且能支持随机读取。
下面我用一段简单的伪代码展示SM4在CBC模式下的参数配置流程。注意IV必须每次随机生成,千万别写死。
function sm4_cbc_encrypt(key, iv, plaintext) {
// 参数验证
if (key.length !== 16 || iv.length !== 16) {
throw new Error("密钥和IV长度必须为16字节");
}
// 密钥扩展
rk = sm4_key_expansion(key);
// 分组处理,PKCS7填充
blocks = pad_pkcs7(plaintext, 16);
ciphertext = [];
prev_block = iv;
for (block in blocks) {
xored = xor(block, prev_block); // CBC异或前一个密文块
enc = sm4_encrypt_block(xored, rk);
ciphertext.append(enc);
prev_block = enc;
}
return ciphertext;
}
代码里最关键的就是“prev_block”的初始值必须用IV。IV如果重复,相同明文块生成的密文块也相同,那CBC模式就失去意义了。以前我在测试环境里偷懒用了全零IV,结果被安全审计发现了,差点被扣绩效……
1.5 知识体系结构
这一章把SM4的“块”拼得差不多该歇口气了。下面这张SVG图帮你梳理本章的核心逻辑,方便后续查看。