SM4算法原理:分组长度、密钥长度、轮函数与S盒设计

好,我们进入正题。SM4算法在国密体系里算是个“老熟人”了。说实话,第一次看它的时候,我心里想:“这不就有点像AES吗?”但真正上手做项目后才发现,它的设计理念和实现细节都有自己的讲究。别急,我们从最基础的参数开始聊。

基础参数:分组长度与密钥长度

SM4的分组长度是128位,密钥长度也是128位。嗯,和AES-128完全一样。但是你别以为它就是个“山寨AES”。它的轮函数结构、S盒生成逻辑和密钥扩展算法都和AES不一样。我当年刚开始做SM4实现时,因为没注意这个差异,直接套了AES的流水线,结果跑出来数据全是错的——后来排查了一整天,才发现是S盒查表地址算错了。

关键参数速览:
  • 分组长度:128 bits(16字节)
  • 密钥长度:128 bits(16字节)
  • 轮数:32轮
  • 轮密钥长度:每轮32 bits(4字节)

你想想看,为什么是128位?这是平衡安全性和性能的结果。过短不安全(比如DES的56位早就被破),过长则不必要的增加了硬件面积。SM4选择128位,既能抵抗暴力破解,又适合在嵌入式设备和智能卡上跑。

轮函数结构:合成置换T

SM4的轮函数核心就一个字——T。这个T不是简单的查表,而是由两部分组成:非线性变换τ和线性变换L。我习惯叫它“合成置换”。

每一轮的具体操作是(假设当前轮输入为 (Xi, Xi+1, Xi+2, Xi+3),轮密钥为 rki):

  1. 计算 B = Xi+1 ⊕ Xi+2 ⊕ Xi+3 ⊕ rki,得到32位中间结果。
  2. 对B进行非线性变换τ:将B拆成4个8位字节,分别送入4个并行的S盒,输出4个8位结果,拼成32位。
  3. 对τ的输出进行线性变换L:一个32位循环移位异或操作。
  4. 最后 Xi+4 = Xi ⊕ T(B),即本轮结果。

说白了,每一轮只更新一个字节块(最左边的那个32位),其他三个直接向右平移。这和AES每轮更新整个状态矩阵是完全不同的策略。我曾在一个FPGA项目里对比过这两种结构,SM4的移位寄存器更容易实现高吞吐。

个人经验: 在软件实现时,轮函数的非线性变换τ是性能瓶颈。很多开源库直接用4个256字节的S盒查表(总共1KB),效率很高。但如果你在资源受限的MCU上跑,建议用“复合域”方式实现S盒——虽然代码复杂点,但能节省几百字节的RAM。

S盒设计

S盒是SM4的非线性核心,也是整个算法唯一引入高度非线性的地方。SM4的S盒是8输入8输出的置换。它的构造方法很有意思:

  • 首先在GF(28)上取逆乘法(和AES一样,用欧拉定理快速计算)。
  • 然后对结果进行仿射变换:一个8×8的二元矩阵乘法加上一个常数向量。
  • 注意,SM4的仿射矩阵和AES不同——这是它们最大的区别。

我当年看过SM4的官方文档,发现它的S盒生成过程里有个“固定子字节”的预处理。为什么要这么做?为了消除代数结构,防止某些代数攻击。说实话,这设计我在做分析时才真正理解——它把原来在GF(2)上的简单关系打散了。

下面给一个S盒的部分查找表(只截取前8个值做个示意):

输入(十六进制)0x000x010x020x030x040x050x060x07
输出(十六进制)0xD60x900xE90xFE0xCC0xE10x3D0xB7
避坑指南: 我曾经在实现SM4时,误将S盒输入当作有符号数处理,结果在循环移位L变换时出现了符号扩展,导致整个算法输出错误。切记:SM4的S盒输入是0~255的无符号字节,不要用 int8_t 存储。

密钥扩展算法

SM4的密钥扩展和加密轮函数几乎一模一样,只是把线性变换L换成了一个更简单的版本(叫 L')。初始密钥16字节分成4个32位字 (MK0, MK1, MK2, MK3),然后通过固定参数 FK 和 CK 生成32个轮密钥。

// 伪代码(仅示意)
uint32_t K[4];
K[0] = MK[0] ^ FK[0];
K[1] = MK[1] ^ FK[1];
K[2] = MK[2] ^ FK[2];
K[3] = MK[3] ^ FK[3];

for (int i = 0; i < 32; i++) {
    uint32_t B = K[i+1] ^ K[i+2] ^ K[i+3] ^ CK[i];
    uint32_t S = T'(B);  // T' 使用 L' 代替常规L
    rk[i] = K[i] ^ S;
    K[i+4] = rk[i];
}

注意这里的 FK 是固定值(系统参数),CK 是中间常数(由循环移位生成)。这样的设计使得密钥扩展和加密用同一套硬件逻辑,节省面积。我记得有个客户问过:既然密钥扩展和加密结构一样,那能不能用同一组S盒?(可以,但要注意轮数不同,加密32轮,扩展也是32轮,正好复用)。

整体结构一张图

我把SM4的轮函数内部结构画成了下面这张SVG流程图。你看完应该能秒懂数据是怎么流动的。

SM4轮函数结构(一轮) Xi+1 Xi+2 Xi+3 rki ⊕ B = 32位 τ:S盒查表 (4个并行8位S盒) L:循环移位 (32位异或链) Xi ⊕ Xi+4 Xi+4 = Xi ⊕ T(B) 其中 T = L ∘ τ

与AES的简单对比

下面这个表格帮你快速对比SM4和AES的关键差异。做算法选型时你会用得上。

项目SM4AES
分组/密钥长度128/128128/128/192/256
轮数32轮10/12/14轮
轮函数结构Feistel-like(每轮更新1/4块)SPN(每轮更新整块)
S盒构造GF(2^8)求逆+仿射(不同矩阵)GF(2^8)求逆+仿射
密钥扩展与加密共用S盒和轮函数独立扩展(Rijndael密钥调度)
硬件实现难度低(复用率高)中(每轮需要S盒+列混合)
我的建议: 如果你的应用需要兼容国密标准,比如金融、政务、物联网身份认证,SM4是首选。但如果只是做内部试验或与非国密系统对接,AES可能更方便。别盲目选型——我曾经在一个项目里同时实现了SM4和AES,最后发现对方只支持AES,白白浪费了两周开发时间。

好了,SM4的核心原理就这么多。分组长度128位、密钥长度128位、32轮、合成置换T、以及那个基于有限域求逆的S盒——这些就是你实现SM4时的所有“武器”。下一张图我建议你自己画一遍轮函数的数据流,画完之后很多细节就刻在脑子里了。