如何选择加密模式(如 AES-GCM、AES-CBC)

加密模式这东西,很多刚接触字段级加密的同学上来就问:“我该用 GCM 还是 CBC?” 说实话,我自己刚入行那会儿也懵过,觉得都是 AES 嘛,换个模式还能差多远?结果后来踩了个大坑,差点把生产数据搞废。今天我把这些经验拆开聊聊,帮你一次选对。

当年我以为只要能加密就行。结果一个项目里用了 AES-CBC-256,没加认证,被攻击者篡改了密文,服务端解出来全是乱码还浑然不知。嗯,从那以后,我每次选模式都会多问自己一句:“我的场景需要防篡改吗?”

主流加密模式速览

先给个对照表,让你心里有数。我平时主要打交道的就是这四种:

模式 是否需要 IV 是否提供认证 并行处理 适合场景
AES-GCM 是(12 字节最佳) 是(内建 GMAC) 是(流水线友好) 字段级加密、网络传输、需要防篡改
AES-CBC 是(随机 16 字节) 否(需额外 HMAC) 否(串行) 文件加密、本地存储(配合 HMAC)
AES-CTR 是(计数器+nonce) 否(需额外认证) 是(可随机解密) 流式加密、高性能场景
AES-CCM 是(要求严格) 是(CBC-MAC) 否(串行) 物联网、受限于算法兼容性

核心观点:如果你的 SDK 接入场景要求“一条龙”搞定加密+认证,无脑选 AES-GCM。CBC 必须配合 HMAC,否则等于裸奔。

一个简单的决策流程

我常用下面这个思路来选模式。你可以把它画成脑筋里的流程图:

加密模式选择决策流程图 选择加密模式决策流程图 需加密的数据字段 需要认证? 是 AES-GCM / CCM 否 需并行/随机访问? 是 AES-CTR + HMAC 否 AES-CBC + HMAC 提醒:无论选哪种,IV 必须随机且永不重用。GCM 对 IV 重复尤其敏感。

个人习惯:我一般先把“是否需要认证”这个问题写在需求文档第一行。如果答案是“是”,直接跳到 GCM;如果“否”,再考虑性能要求。别纠结太久,80% 的字段级加密场景选 GCM 都稳得一匹。

具体场景建议

结合我实际做过的几个项目,给你一些更落地的建议:

  • 用户手机号、身份证号加密(API 调用频繁)——选 AES-GCM。自带认证,省去额外 HMAC 计算,性能也够。我上一个项目就是这么配的,线上跑了两年没出过认证问题。
  • 本地配置文件加密(离线环境)——AES-CBC + HMAC-SHA256 够用。注意 IV 用 16 字节,每次加密随机生成。我遇到过有人把 IV 写死在代码里……嗯,那跟没加密没啥区别。
  • 实时日志流加密(大吞吐)——AES-CTR 配合单独的消息认证。CTR 可以分段并行解密,适合流式环境。但别忘了每次启动都要换 nonce 和 counter 起始值。
  • 物联网设备端字段加密(资源受限)——AES-CCM 或者 GCM。CCM 在旧硬件上支持更广,但计算开销大。如果你能选 GCM,优先 GCM。

避坑指南:我曾经踩过的几个雷

这些坑我实打实踩过,你记住一个就能省很多调试时间。

IV 重复,灾难级别:GCM 模式下,同一密钥下 IV 重复一次,攻击者就能恢复你的密钥流。我曾经在测试环境忘了重置 nonce 计数器,导致两个密文用了同一个 IV,还好只是测试数据。生产环境千万别犯,建议用随机 12 字节 IV + 全局计数器保底。

  • CBC 不加 HMAC 就上线——没错,我干过。结果被安全审计直接打回,理由是“填充预言攻击”。从那以后我给自己立了个规矩:CBC 必须配 HMAC,并且 HMAC 要在密文后用额外字段存储,不在同一消息里混淆。
  • 盲目选择最大位宽——有人以为 AES-256 一定比 AES-128 安全。但 GCM 模式在 128 位下的性能优势明显,256 位会让部分硬件加速失效。说实话,对于字段级数据,128 位 AES 加认证完全够用,别为了“看起来安全”牺牲性能。
  • 忽略附加数据的认证——GCM 支持 AAD(附加认证数据),很多人只加密 payload,不认证相关字段(比如用户 ID、时间戳)。建议把上下文信息放进 AAD,防止攻击者把加密结果挪到其他用户头上。

代码示例:Python 中初始化两种模式

我一般用 cryptography 库。给你两个快速启动片段:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import hmac, hashes
import os

# --- AES-GCM (推荐) ---
key = os.urandom(32)      # AES-256
iv = os.urandom(12)       # 推荐 12 字节
cipher = Cipher(algorithms.AES(key), modes.GCM(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(b"手机号: 138****1234") + encryptor.finalize()
# 认证标签自动生成,可通过 encryptor.tag 取出(最后 16 字节)

# --- AES-CBC + HMAC (需手动组合) ---
key_enc = os.urandom(32)  # 加密密钥
key_mac = os.urandom(32)  # HMAC 密钥,和加密密钥区分开
iv_cbc = os.urandom(16)
cipher_cbc = Cipher(algorithms.AES(key_enc), modes.CBC(iv_cbc))
encryptor_cbc = cipher_cbc.encryptor()
ct = encryptor_cbc.update(b"身份证: 110101******1234") + encryptor_cbc.finalize()
# 计算 HMAC(加密后的密文 + IV)
h = hmac.HMAC(key_mac, hashes.SHA256())
h.update(ct)
h.update(iv_cbc)
mac = h.finalize()
# 最终存储:iv_cbc + ct + mac

原则:不要自己拼凑模式。能用现成的高层封装(如 Fernet)就别手写组合。我们的 SDK 内部对 GCM 做了大量优化,你直接调 encrypt/decrypt 就好。

总结一句话

选加密模式,就三步:先问要不要认证 → 要→GCM;不要→看性能要求→CTR 或 CBC。记住 IV 别重用、CBC 一定配 HMAC、GCM 别省 AAD。把这些记牢,你的字段级加密就稳了九成。