SM4对称加密算法:从原理到实战

对称加密,说白了就是加密和解密用同一把钥匙。你在网上银行转账、用支付,背后都有对称加密在默默工作。国密标准里的SM4,就是咱们国家自己的对称加密算法。我最早接触SM4是在一个政务系统项目里,当时客户明确要求“必须用国密,不能用AES”。嗯,从那以后我就把SM4研究了个透。

SM4算法原理:Feistel结构

SM4的核心结构叫Feistel网络。这个名字听着唬人,其实原理很简单——把数据切成两半,左边不动,右边拿去跟轮密钥做运算,然后左右交换。重复这个操作若干轮,就完成了加密。

具体来说,SM4把128位明文分成4个32位的字。每一轮只更新其中一个字,其他三个字直接移位。我画个图你一看就明白:

SM4 Feistel 结构(一轮) X[0] X[1] X[2] X[3] 轮函数 F (含S盒+移位) 轮密钥 rk ⊕ X[1] X[2] X[3] X[0]⊕F 一轮操作:X[0]不动,X[1]→X[0],X[2]→X[1],X[3]→X[2], X[3]与轮函数F(X[1],X[2],X[3],rk)异或后成为新的X[3]

SM4总共跑32轮。32轮之后,把四个字倒序输出,就是密文。解密时完全一样,只是轮密钥的顺序反过来。这个对称性,是Feistel结构最巧妙的地方。

核心要点:SM4的Feistel结构保证了加解密可以用同一套代码,只是轮密钥顺序不同。这在硬件实现上特别省资源。

密钥扩展与轮函数

SM4的密钥是128位,但需要生成32个32位的轮密钥。这个过程叫密钥扩展。我当初自己实现时,在这个环节踩过坑——轮密钥算错了,加解密死活对不上。

密钥扩展流程

密钥扩展分三步走:

  1. 初始化:把128位密钥分成4个32位字 MK[0..3]
  2. 异或固定参数:K[i] = MK[i] ⊕ FK[i],FK是系统参数(固定值)
  3. 迭代生成:rk[i] = K[i+4] = K[i] ⊕ T'(K[i+1] ⊕ K[i+2] ⊕ K[i+3] ⊕ CK[i])

这里的T'变换和加密轮函数里的T变换很像,只是线性移位不同。CK是32个固定常数,每个都不一样。

我的经验:调试密钥扩展时,建议先拿标准测试向量对一下前几个轮密钥。如果第一个轮密钥就错了,后面全白搭。我曾经花了一下午查bug,最后发现是CK数组抄错了一个字节。

轮函数F

每一轮的轮函数F包含四个操作:

步骤 操作 说明
1 异或 B = X[1] ⊕ X[2] ⊕ X[3] ⊕ rk
2 S盒替换 每个字节查S盒,非线性变换
3 线性移位L B' = B ⊕ (B<<2) ⊕ (B<<10) ⊕ (B<<18) ⊕ (B<<24)
4 输出 F = B',与X[0]异或得到新X[3]

S盒是SM4唯一的非线性部件。它把8位输入映射成8位输出,一共256个值。这个S盒是设计好的,你不能自己改。改了就不是SM4了。

注意:线性移位L在密钥扩展中用的是简化版L',只做13位和23位的循环左移。加密轮函数里用的是完整版L。这两个不一样,千万别搞混。

SM4的工作模式:ECB、CBC、CTR

算法本身只处理128位数据块。实际应用中,数据可能很长,也可能很短。这就需要工作模式来规定怎么处理多块数据。

ECB模式(电子密码本)

最简单粗暴的模式。每块数据独立加密,互不影响。

  • 优点:可以并行加密,速度快
  • 缺点:相同的明文块产生相同的密文块,有模式泄露风险
  • 适用场景:加密随机数据(如密钥本身),不适合加密结构化数据

我个人很少用ECB。有一次做图片加密演示,用ECB模式加密后,图片轮廓居然还能看出来。这就是模式泄露的典型表现。

CBC模式(密码分组链接)

每个明文块先与前一个密文块异或,再加密。第一个块需要初始化向量IV。

  • 优点:相同的明文块产生不同的密文块,安全性高
  • 缺点:不能并行加密,一个块出错会影响后续所有块
  • 适用场景:文件加密、数据库加密

避坑指南:IV必须随机生成,且每次加密都要用不同的IV。我曾经见过有人把IV写死在代码里,结果审计时被判定为安全漏洞。IV不是密钥,但同样重要。

CTR模式(计数器模式)

用计数器生成密钥流,再与明文异或。本质上是把分组密码当流密码用。

  • 优点:可以并行加解密,支持随机访问
  • 缺点:计数器不能重复,否则会泄露密钥流
  • 适用场景:网络通信、磁盘加密

CTR模式是我在项目中用得最多的。它支持随机访问,解密一个块不需要解密前面的块。这在加密大文件时特别有用——你想读文件中间某一段,不用从头解密。

模式 并行性 错误传播 是否需要IV 推荐场景
ECB 支持 无 否 随机数据加密
CBC 不支持 影响后续块 是 文件/数据库加密
CTR 支持 无 是 网络通信/流加密

Python实现SM4加解密

Python里实现SM4,最方便的是用gmssl库。这个库是国密算法的官方Python实现,支持SM2、SM3、SM4。

先安装:

pip install gmssl

ECB模式示例

from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT

# 密钥必须是16字节
key = b'1234567890123456'
data = b'Hello, SM4! This is a test message.'

# 加密
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(key, SM4_ENCRYPT)
ciphertext = crypt_sm4.crypt_ecb(data)
print(f'密文(hex): {ciphertext.hex()}')

# 解密
crypt_sm4.set_key(key, SM4_DECRYPT)
plaintext = crypt_sm4.crypt_ecb(ciphertext)
print(f'明文: {plaintext.decode()}')

CBC模式示例

from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
import os

key = b'1234567890123456'
iv = os.urandom(16)  # 随机生成IV
data = b'Hello, SM4! This is a test message.'

# 加密
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(key, SM4_ENCRYPT)
ciphertext = crypt_sm4.crypt_cbc(iv, data)
print(f'IV(hex): {iv.hex()}')
print(f'密文(hex): {ciphertext.hex()}')

# 解密
crypt_sm4.set_key(key, SM4_DECRYPT)
plaintext = crypt_sm4.crypt_cbc(iv, ciphertext)
print(f'明文: {plaintext.decode()}')

CTR模式示例

from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
import os

key = b'1234567890123456'
iv = os.urandom(16)  # CTR模式也需要IV(作为计数器的初始值)
data = b'Hello, SM4! This is a test message.'

# 加密
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(key, SM4_ENCRYPT)
ciphertext = crypt_sm4.crypt_ctr(iv, data)
print(f'IV(hex): {iv.hex()}')
print(f'密文(hex): {ciphertext.hex()}')

# 解密(CTR模式加解密是同一个操作)
crypt_sm4.set_key(key, SM4_DECRYPT)
plaintext = crypt_sm4.crypt_ctr(iv, ciphertext)
print(f'明文: {plaintext.decode()}')

小技巧:CTR模式的加解密函数是一样的。因为CTR本质上是生成密钥流再异或,异或两次就还原了。所以crypt_ctr在加密和解密时调用同一个方法就行。

实战中的注意事项

最后分享几个我在项目中积累的经验:

  • 密钥管理:密钥不要硬编码在代码里。用密钥管理系统或硬件安全模块(HSM)存储。
  • IV生成:每次加密都要生成新的IV。用os.urandom(16)生成,不要用随机数种子固定的伪随机数。
  • 数据填充:ECB和CBC模式要求数据长度是16字节的倍数。gmssl库默认使用PKCS7填充,你不需要手动处理。
  • 性能考量:CTR模式可以并行加速。如果加密大量数据,优先选CTR。

重要:生产环境中不要自己实现SM4算法。直接用gmssl、OpenSSL(1.1.1+版本支持SM4)或硬件加密卡。自己实现的算法容易有侧信道攻击漏洞,比如时间攻击、功耗分析。

SM4的算法原理和实现就讲到这里。你想想看,Feistel结构、密钥扩展、工作模式,这些概念其实都不复杂。关键是动手写代码跑一遍,才能真正理解。我建议你拿上面三个示例代码跑一下,看看输出结果,再改改数据试试。