1、SM4算法简介

国标背景与前世今生

SM4 是什么?说白了就是咱们国家自己设计的分组密码算法。2012 年它变成了国家标准(GB/T 32907),2016 年又被采纳为 ISO 国际标准。你想想看,一个算法能从国内标准走到国际,背后肯定有它的硬道理。

我记得早年做嵌入式安全项目时,客户要求必须用国密算法。那时候 SM4 的资料还不多,网上能找到的参考实现也就那么几个。我啃了一天标准文档,发现它的结构跟 AES 不太一样,但轮数更少、实现起来反而更轻量。

核心要点:

  • 对称分组密码,加密密钥=解密密钥
  • 密钥长度 128 位,分组大小也是 128 位
  • 轮函数基于 Feistel 结构,但扩展是非线性置换
  • 国际标准 ISO/IEC 18033-3:2010/Amd 1:2020

算法结构概览

SM4 的结构其实不复杂,我画个图你一看就懂。

SM4 算法总体结构 密钥扩展 轮密钥 K0..K31 明文 (128 bits) 32 轮迭代 含 S 盒 & 线性变换 每轮使用不同轮密钥 反序变换 R 密文 (128 bits) 结构特点 Feistel 变体 S盒 + 循环移位 与 AES 不同
小提示:我个人习惯把 SM4 的结构记成“32 轮 Feistel + 密钥扩展 + 反序”。你在做固件移植时,轮密钥的生成经常是容易被搞错的地方——注意它是从主密钥通过固定置换和 S 盒派生的。

密钥长度与分组大小

SM4 的密钥长度是 128 位,分组大小也是 128 位。嗯,跟 AES-128 一样长,但轮数只有 32 轮。我见过有人问:「密钥能设成 256 位吗?」答案是不能——标准只定义了 128 位。不过在实际项目中,你可以在上层协议里做密钥派生,但算法核心不变。

参数 SM4 AES-128 DES
密钥长度128 bits128 bits56 bits
分组大小128 bits128 bits64 bits
轮数321016
结构Feistel 变体SPNFeistel

为什么是 128 位?从安全角度看,128 位的密钥空间足够抵抗暴力破解。你想想看,2^128 次尝试,就算用全球的算力也搞不定。加密时,明文被切成 128 位的块,如果最后一块不够长,就需要填充——我在项目中默认用 PKCS#7 填充,兼容性最好。

加解密流程

搞清楚了密钥和分组,咱们看看实际怎么加解密。流程其实是对称的:加密用 32 轮迭代,解密只是把轮密钥顺序倒过来用。为什么能这样?因为轮函数本身就是可逆的。

  1. 密钥扩展:128 位主密钥通过 32 轮非线性变换生成 32 个 32 位轮密钥。
  2. 分组处理:明文分组被拆成 4 个 32 位字 (X0, X1, X2, X3)。
  3. 32 轮迭代:每轮对 X3 做非线性置换 + 线性变换,再异或跟轮密钥,最后左移。
  4. 反序变换:最后 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 盒是固定查找表,你可以预计算好放在代码里,不用每次算。但注意查表要防侧信道攻击——定时比较、分支泄漏都得小心。