SM4 的数学基石:有限域与 S 盒
说实话,很多人学 SM4 上来就啃 S 盒,觉得就是一张 16×16 的查找表。但我个人习惯,先把有限域搞清楚。没有这个基础,你后面做白盒实现或者侧信道防护时,一定会踩坑。我就在一个嵌入式项目里吃过亏 —— 没搞懂字节在 GF(2⁸) 下的乘法规则,导致 S 盒查表结果跟标准库对不上。折腾了两天才发现,其实是域生成元选错了。
好了,咱们一步步来。
有限域 GF(2⁸) 是什么?
有限域,说白了就是一组数,个数有限,还能做加减乘除(除法除了 0 都能做)。SM4 用的是 GF(2⁸),也就是 256 个元素。每个元素可以用一个字节表示,比如 0x57 代表多项式 x⁶ + x⁴ + x² + x + 1。
注意,这里的加法和我们平时用的整数加法不一样。在 GF(2⁸) 里,加法就是按位异或。为什么?因为系数只有 0 和 1,加法不进位,直接 xor 就行。你想想看,这样设计的好处就是硬件实现时特别快,一个异或门就能搞定。
| 运算 | 整数环 | GF(2⁸) |
|---|---|---|
| 加法 | 带进位加 | 按位异或 (⊕) |
| 乘法 | 普通乘法后取模 | 多项式乘法后模不可约多项式 |
| 模数 | 256 | x⁸ + x⁴ + x³ + x + 1 |
我记得最初看 SM4 标准文档时,那个不可约多项式是 0x11B(二进制 1 0001 1011)。这个多项式是固定的,不能改,改了 S 盒就全变了。
S 盒的构造逻辑
S 盒是 SM4 唯一的非线性部件。它接收一个字节,输出另一个字节。怎么来的?两步走:
- 求逆元:在 GF(2⁸) 里找输入字节的乘法逆元(0 的逆元定义为 0)。
- 仿射变换:对逆元做一个线性变换,再异或一个常数 0xD3。
为什么要加仿射变换?如果不加,S 盒就只是求逆,代数性质太差,容易被人用代数攻击攻破。加了仿射,好比给密码套了层“马甲”,让输入输出关系变得更复杂。
这里我贴一段用 C 语言生成 S 盒的核心代码,你自己可以跑跑看,验证结果是否跟标准一致。
// 构建 GF(2⁸) 乘法逆元表 (利用指数表和对数表)
uint8_t inv[256];
uint8_t exp[512], log[256];
// 首先构造指数表:exp[i] = 3^i (生成元 3)
exp[0] = 1;
for (int i = 1; i < 511; i++) {
exp[i] = (exp[i-1] << 1) ^ (exp[i-1] & 0x80 ? 0x11B : 0);
}
// 然后对数表:log[exp[i]] = i (i < 256)
for (int i = 0; i < 255; i++) log[exp[i]] = i;
log[0] = 0; // 0 没有逆元
// 求逆元:inv[x] = exp[255 - log[x]],x≠0
for (int x = 1; x < 256; x++) {
inv[x] = exp[255 - log[x]];
}
inv[0] = 0;
// 仿射变换
uint8_t sbox[256];
for (int x = 0; x < 256; x++) {
uint8_t y = inv[x];
uint8_t z = 0;
// 矩阵乘法 + 常数 0xD3
z ^= (y & 0x01) ? 0x01 : 0;
z ^= (y & 0x02) ? 0x83 : 0;
// ... 省略中间 6 行
z ^= (y & 0x80) ? 0x8B : 0;
z ^= 0xD3;
sbox[x] = z;
}
sbox[row][col] 构造,一定要确认 row = (input >> 4),col = (input & 0x0F)。
S 盒的属性验证
为了确认你生成的 S 盒是对的,通常要检查几个性质:
- 双射性:每个输出值唯一,256 个输出互不重复。
- 差分均匀性:理想值是 4,实际 SM4 S 盒最大差分概率是 4/256。
- 线性逼近:最大线性偏差大概在 80/512 左右,略低于理论最优,但够用。
我习惯用 Python 快速算一遍:假如输出不是双射,肯定是你仿射矩阵写错了。
一张图看懂有限域与 S 盒的关系
下面这张 SVG 图总结了从输入字节到 S 盒输出的完整数据流,以及有限域在其中的角色。
实践中的注意事项
最后聊几个实战中容易忽略的点:
- 查表速度 vs 算力:如果你在 ARM Cortex-M 上跑,S 盒预计算好,查表最省事。但在 ASIC 或 FPGA 上,有时你会用组合逻辑直接算 S 盒,省掉一个 256 字节的 ROM。两种做法我都试过,查表版功耗更低。
- 字节序:SM4 自身用小端序,但 S 盒定义时输入输出都是大端表示(高 4 位为行)。我见过有人把 0x01 当作最高位是 0 的行,结果全错。
- 侧信道防护:如果直接查表,很容易被能量分析攻破。我通常在 S 盒外部加掩码,或者改用门限实现。这部分后面章节会详细讲。
嗯,有限域和 S 盒就讲到这里。它们构成了 SM4 最核心的代数基础,后面的密钥扩展、轮函数都绕不开这两个概念。你如果能亲手把 S 盒生成一遍,对这个算法理解就深一层。