SM4密钥扩展算法归纳总结
密钥扩展,说白了就是把你的初始密钥变成32个轮密钥。SM4的加密解密共用这32个轮密钥,只不过解密时倒着用。这个过程不复杂,但细节特别容易翻车。有一次我在做项目的时候,看到新人写的代码里密钥扩展部分处理得很糟糕,结果加解密结果对不上,排查了半天才发现是扩展出来的轮密钥顺序搞反了。嗯,这里我们就把它彻底讲清楚。
密钥扩展基础
SM4的密钥长度是128位,也就是16个字节。你可以把它想象成一个4×4的矩阵,每个格子是1字节。扩展过程就是从这16字节的初始密钥,生成32个32位的轮密钥。
- 输入:16字节初始密钥,记为
MK - 输出:32个32位轮密钥,记为
rk0, rk1, ..., rk31 - 核心运算:非线性变换τ和线性变换L'
我个人习惯在写密钥扩展代码时,先确认字节序。SM4标准默认是大端序,但如果你的系统用的是小端序,转换时一定要小心。我遇到过几次因为字节序没对齐导致轮密钥算错的情况。
密钥扩展参数解析
密钥扩展需要两组固定参数。一组叫系统参数FK,另一组叫固定参数CK。这两组参数的值都是标准写死的,你不需要自己推导,直接拿来用就行。
系统参数FK:一共4个32位常数,用于初始化密钥状态。
| 参数 | 值(十六进制) |
|---|---|
FK0 |
0xA3B1BAC6 |
FK1 |
0x56AA3350 |
FK2 |
0x677D9197 |
FK3 |
0xB27022DC |
固定参数CK:一共32个32位常数,每个轮密钥生成时使用一个。CK的生成规则也是固定的,我建议你直接查表,没必要每次重新算,浪费时间还容易出错。
记得自己第一次看SM4标准文档时,对着FK的表格看了好久,心想为什么是这几个随机数。后来明白了,这些参数的作用就是打散密钥的统计特性,防止攻击者从密钥中提取规律。别纠结它们怎么来的,用好它们就行。
密钥扩展流程
整个流程分两步走。第一步是初始化,第二步是迭代生成32个轮密钥。
- 初始化:将初始密钥MK与FK进行异或,得到4个中间状态值:
K0, K1, K2, K3。 - 迭代生成:对于
i = 0到31,计算rki = Ki+4 = Ki ⊕ T'(Ki+1 ⊕ Ki+2 ⊕ Ki+3 ⊕ CKi)。
这里的 T'() 是一个合成变换,先过非线性变换τ(查S盒),再过线性变换L'(移位异或)。
我曾经在调试时发现,密钥扩展中的S盒查表和加密算法中的S盒是同一个S盒。千万别自己另搞一套,否则结果肯定对不上。这个坑我踩过,查了三天才找到原因。
SVG:密钥扩展流程结构图
下面这张图展示了密钥扩展的完整流程,从初始密钥输入到32轮密钥输出,你可以对照着看。
代码实现与避坑指南
下面是密钥扩展的Python实现伪代码。我故意把S盒和线性变换抽象成函数,这样整体流程更清晰。
def sm4_key_expansion(mk):
"""
mk: 16字节初始密钥
返回: 32个32位轮密钥列表
"""
# 系统参数
FK = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC]
# 固定参数(仅示例前4个,实际32个)
CK = [0x00070E15, 0x1C232A31, 0x383F464D, 0x545B6269, ...]
# 1. 将mk拆成4个32位字
K = [0, 0, 0, 0]
for i in range(4):
K[i] = bytes_to_word(mk[i*4:(i+1)*4]) ^ FK[i]
# 2. 迭代生成轮密钥
rk = []
for i in range(32):
temp = K[i+1] ^ K[i+2] ^ K[i+3] ^ CK[i]
# T' = τ + L' 合成变换
temp = sm4_tau(temp) # 查S盒
temp = sm4_l_prime(temp) # 线性变换L'
ki = K[i] ^ temp
rk.append(ki)
K.append(ki) # 扩展状态列表
return rk
注意这里的sm4_l_prime是密钥扩展专用的线性变换,它不是加密轮函数里的那个L。L'对32位输入做的是循环移位后的异或,而L做的是不一样的移位组合。用错了,整个密钥链就断了。
密钥扩展常见问题
| 问题 | 后果 | 解决方案 |
|---|---|---|
| 字节序错误 | 轮密钥与标准不一致 | 明确使用大端序,必要时做转换 |
| S盒复用错误 | 加密解密结果不匹配 | 使用标准SM4 S盒,与加密一致 |
| 线性变换混淆 | 密钥扩展结果错误 | 区分L'与L,不可混用 |
| CK列表不全 | 越界或使用错误参数 | 预置完整32个CK值 |
你想想看,密钥扩展这部分其实就两个要点。一是参数别用错,二是流程别跳步骤。只要把FK、CK、S盒、L'这四个东西对齐了,剩下的就是按部就班地迭代32次。
我建议在实现密钥扩展后,做一个简单的自检:拿一组已知的测试向量,把初始密钥和生成的轮密钥逐轮打印出来,跟标准答案对比。这一步能提前发现80%的问题。