2. 对称加密原理:AES、DES、SM4 等算法原理与使用场景

聊对称加密前,我先说个真实教训。几年前给一个金融 SDK 封装加解密模块,我图省事直接用了 DES,结果被安全团队打回来重写。客户要求支持 SM4,还得兼容海外 AES-256。从那以后我养成了先列算法清单的习惯。

对称加密,说白了就是加密和解密用同一把密钥。速度快,适合大批量数据。但密钥分发是个麻烦事——你想想,两个人怎么安全地拿到同一个密钥?这是非对称加密要解决的。不过今天先只讲对称部分。

对称加密原理总览 共享密钥 明文 加密算法 密文 发送方 用密钥加密明文 接收方 用同一密钥解密 AES (128/192/256) DES / 3DES SM4 (国密) 常见工作模式 ECB · CBC · CFB · OFB · CTR · GCM 填充模式:PKCS7 / ANSI X.923 / ZeroPadding 核心:加密和解密使用完全相同的密钥

2.1 AES:当今对称加密的标配

AES(Advanced Encryption Standard)是 NIST 在 2001 年发布的标准,取代了 DES。我个人习惯叫它“三兄弟”:AES-128、AES-192、AES-256,密钥长度不同,轮数也不一样。128 位密钥加密 10 轮,192 位 12 轮,256 位 14 轮。

它的核心结构是 SPN(Substitution-Permutation Network),说白了就是反复做四件事:字节代换、行移位、列混合、轮密钥加。最后再加一轮,去掉列混合。

使用场景:

  • HTTPS 的 TLS 握手后的对称加密(常用 AES-128-GCM)
  • 文件加密(ZIP 压缩包、云存储)
  • VPN 隧道加密(IPSec 中的 ESP 协议)
  • 嵌入式设备(硬件 AES 加速随处可见)

我在封装 SDK 时,默认用 AES-256-GCM。为什么?GCM 自带认证,一次搞定加密+完整性校验,省去额外算 HMAC 的麻烦。而且很多云服务商的 KMS 只认 AES-256,比如阿里云 KMS、AWS KMS。

下面是 Python 中用 pycryptodome 实现 AES-256-GCM 加密解密的例子:

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes

key = get_random_bytes(32)      # 256 bit
nonce = get_random_bytes(12)    # GCM 推荐 12 字节 nonce
plaintext = b"Hello SDK!"

cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)

print("密文:", ciphertext.hex())
print("认证标签:", tag.hex())

# 解密
cipher_dec = AES.new(key, AES.MODE_GCM, nonce=nonce)
data = cipher_dec.decrypt_and_verify(ciphertext, tag)
print("解密:", data)

我的一点习惯:nonce(IV)不要硬编码,每次加密都要生成新的随机数。如果 reuse nonce,GCM 会完全沦陷。我自己踩过这个坑——测试时图省事写了个固定 nonce,差点留下安全漏洞。

2.2 DES 与 3DES:老将退场

DES(Data Encryption Standard)诞生于 1977 年,56 位密钥在今天的算力面前等于形同虚设。我记得 2018 年有个老旧的支付接口还在用 DES,我建议客户尽快升级,被一句“一直没问题”给怼回来了。结果第二年 PCI DSS 合规审计没通过,还是乖乖换了 AES。

3DES 是过渡方案,用三个 56 位密钥做三次 DES 运算(EDE 模式),有效密钥长度 112 位或 168 位。但性能差,只适合旧系统兼容。NIST 已经在 2023 年正式废弃 3DES,新项目绝不要碰。

避坑指南:我曾经接手一个遗留系统,数据库里存了 3DES 加密的用户手机号。为了兼容,解密接口仍然保留 3DES,但新写入数据全部改用 AES-256。过渡期跑了半年,确认旧数据全部轮替完毕后,才彻底删除 3DES 代码。如果你遇到类似情况,建议先在配置里做“双算法支持”,别直接删老算法。

2.3 SM4:国密标准,国内合规必备

SM4 是中国国家密码管理局发布的对称加密算法(GB/T 32907-2016),128 位密钥,32 轮迭代,结构跟 AES 类似但具体实现不同。很多国内项目必须过“密评”,比如银行、政务、医保系统,SM4 基本是硬性要求。

SM4 的工作模式通常跟 AES 一样,CBC、CTR、GCM 都有。我在做银行 SDK 时,对方要求同时支持 SM4-CBC/PKCS7 和 SM4-GCM,后端一个接口兼容两套,全靠算法标识符区分。

Python 中可以使用 gmssl 库:

from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT

key = bytes.fromhex('0123456789abcdeffedcba9876543210')
iv  = bytes.fromhex('00000000000000000000000000000000')  # 测试用,生产随机

cipher = CryptSM4()
cipher.set_key(key, SM4_ENCRYPT)
plaintext = b"国密测试数据"
ciphertext = cipher.crypt_cbc(iv, plaintext)   # CBC 模式

print("SM4密文:", ciphertext.hex())

使用场景:

  • 国内银行、证券、第三方支付敏感数据加密
  • 政务信息系统(电子政务、社保、公积金)
  • 合规要求:等级保护三级、密码应用安全性评估

2.4 算法对比速查表

算法 密钥长度 分组大小 轮数 性能 推荐度
AES128/192/256 bit128 bit10/12/14快(硬件加速)★★★★★
DES56 bit64 bit16慢★(废弃)
3DES112/168 bit64 bit48很慢★(废弃)
SM4128 bit128 bit32较快(软件实现)★★★★(国密场景)

2.5 关键参数:工作模式与填充

算法选定了,还有两个事必须敲定:工作模式和填充方式。我见过不少联调事故,两边一个用 CBC 一个用 ECB,死活对不上。所以 SDK 设计时最好把模式写到请求参数里,甚至明文传递一个 {"alg":"AES","mode":"GCM","iv":"..."} 头部。

下面是个常用模式的对比表:

模式 特点 安全性 典型场景
ECB每个分组独立加密,相同明文得相同密文低(模式攻击)避免使用
CBC前一分组密文参与下一分组加密,需要 IV高(需防 padding oracle)文件加密(配合 HMAC)
CFB流模式,可将分组密码当流密码用中等少量数据实时加密
CTR计数器模式,可并行加密,不需要填充高磁盘加密、网络流
GCMCTR + GMAC 认证,同时提供保密和完整性极高HTTPS、API 加密(强烈推荐)

关于填充:分组加密要求明文长度是分组的整数倍(128 位 = 16 字节)。ECB、CBC 需要填充。最常用的是 PKCS7:缺几个字节就补几个值为缺失字节数的字节。比如缺 3 字节,就补 0x03 0x03 0x03。CTR、GCM 属于流模式,不需要填充。

我的习惯:新项目一律使用 GCM 或 CTR+HMAC。CBC 虽然成熟,但 padding oracle 攻击风险始终存在(需要额外做 MAC 校验)。如果你必须用 CBC,一定记得解密后先验证 HMAC,再解填充。

2.6 对称加密在 SDK 封装中的落地要点

给 SDK 封装加解密时,我总结出几条铁律:

  • 算法抽象:不要硬编码算法标识,用工厂模式根据入参 algorithm + mode + padding 动态选择。
  • IV 策略:每次加密自动生成随机 IV,塞进密文前 16 字节(或作为单独字段)。解密时先取出 IV。
  • 兼容旧算法:参考“双算法过渡”策略,通过版本号或配置决定使用哪套算法。
  • 密钥独立:对称密钥不要写在代码里,从密钥管理服务(KMS)获取或从配置文件加密存储。

我过去在某个 SDK 里用 JSON 协议传输加密数据,结构大致长这样:

{
  "alg": "AES-256-GCM",
  "iv": "b64...",
  "tag": "b64...",
  "ciphertext": "b64..."
}

这样前端后端都能解析,而且加解密两边各说各话时,一眼就能看出是哪部分参数不匹配。