1、SM4算法简介
国标背景与前世今生
SM4 是什么?说白了就是咱们国家自己设计的分组密码算法。2012 年它变成了国家标准(GB/T 32907),2016 年又被采纳为 ISO 国际标准。你想想看,一个算法能从国内标准走到国际,背后肯定有它的硬道理。
我记得早年做嵌入式安全项目时,客户要求必须用国密算法。那时候 SM4 的资料还不多,网上能找到的参考实现也就那么几个。我啃了一天标准文档,发现它的结构跟 AES 不太一样,但轮数更少、实现起来反而更轻量。
核心要点:
- 对称分组密码,加密密钥=解密密钥
- 密钥长度 128 位,分组大小也是 128 位
- 轮函数基于 Feistel 结构,但扩展是非线性置换
- 国际标准 ISO/IEC 18033-3:2010/Amd 1:2020
算法结构概览
SM4 的结构其实不复杂,我画个图你一看就懂。
密钥长度与分组大小
SM4 的密钥长度是 128 位,分组大小也是 128 位。嗯,跟 AES-128 一样长,但轮数只有 32 轮。我见过有人问:「密钥能设成 256 位吗?」答案是不能——标准只定义了 128 位。不过在实际项目中,你可以在上层协议里做密钥派生,但算法核心不变。
| 参数 | SM4 | AES-128 | DES |
|---|---|---|---|
| 密钥长度 | 128 bits | 128 bits | 56 bits |
| 分组大小 | 128 bits | 128 bits | 64 bits |
| 轮数 | 32 | 10 | 16 |
| 结构 | Feistel 变体 | SPN | Feistel |
为什么是 128 位?从安全角度看,128 位的密钥空间足够抵抗暴力破解。你想想看,2^128 次尝试,就算用全球的算力也搞不定。加密时,明文被切成 128 位的块,如果最后一块不够长,就需要填充——我在项目中默认用 PKCS#7 填充,兼容性最好。
加解密流程
搞清楚了密钥和分组,咱们看看实际怎么加解密。流程其实是对称的:加密用 32 轮迭代,解密只是把轮密钥顺序倒过来用。为什么能这样?因为轮函数本身就是可逆的。
- 密钥扩展:128 位主密钥通过 32 轮非线性变换生成 32 个 32 位轮密钥。
- 分组处理:明文分组被拆成 4 个 32 位字 (X0, X1, X2, X3)。
- 32 轮迭代:每轮对 X3 做非线性置换 + 线性变换,再异或跟轮密钥,最后左移。
- 反序变换:最后 4 个字反序排列得到密文。
解密呢?直接把轮密钥数组逆序,用同一套代码就能解。我曾在 ARM Cortex-M4 上做过优化,32 轮一次跑完大概只要几十微秒。
/* SM4 一轮加密伪代码(简化) */
void sm4_one_round(uint32_t x[4], uint32_t rk) {
uint32_t tmp = x[3];
tmp = sm4_nonlinear(tmp); // S 盒替换
tmp = sm4_linear(tmp); // 循环移位 + 异或
x[0] ^= (tmp ^ rk);
// 然后循环左移: x[1]->x[0], x[2]->x[1], x[3]->x[2], x[0]->x[3]
}
我的一点经验总结
SM4 虽然是个老算法,但在存储密钥边界隔离的场景下非常实用。举个例子,我在做安全芯片方案时,主密钥存储在硬件安全单元里,SM4 加密引擎直接跑在 SRAM 里,中间轮密钥不会暴露到总线。这样就算攻击者能读内存,也拿不到原始密钥。
对了,顺便提一句:SM4 的 S 盒是固定查找表,你可以预计算好放在代码里,不用每次算。但注意查表要防侧信道攻击——定时比较、分支泄漏都得小心。