AES 只是“一块”算法
AES 每次只处理 16 字节的分组。要让它能加密任意长度的数据,需要工作模式(GCM、CBC 等)把分组串起来。所以只说“我用 AES 加密”是不够的,还必须说明模式和参数。
模式对比
| 模式 | 是否认证 | IV 要求 | 建议 |
|---|---|---|---|
| GCM | 是(AEAD) | 12 字节随机,绝不可复用 | 首选,兼顾保密与防篡改 |
| CBC | 否 | 16 字节随机 | 需自行加 HMAC 才能防篡改 |
| ECB | 否 | 无 | 不要用:相同明文块产生相同密文块 |
IV(初始向量)为什么不能复用
GCM 用 IV 与计数器生成密钥流。同一个密钥下重复使用 IV,会让两段密文的密钥流重合,攻击者异或即可还原明文,同时认证标签也会失效。实践中每次加密都随机生成 IV,并把它与密文一起存储(IV 无需保密)。
正确的加密流程
- 随机生成 12 字节 IV;
- 用 AES-GCM 加密,得到密文与认证标签;
- 把
IV + 密文 + 标签一起存储或传输(注意不要把 IV 也加密); - 解密时必须验证标签,验证失败就直接报错,绝不使用部分明文。
密钥从哪来
用户口令不能直接当密钥——它的熵远低于 128 位。应通过 KDF(PBKDF2、scrypt、Argon2)从口令派生密钥,并设置足够的工作因子;或直接用 CSPRNG 生成随机密钥,再讨论如何安全保存。
常见问题
IV 和盐是一回事吗?不是:IV 用于加密过程、保证同一明文每次密文不同;盐用于派生密钥、保证相同口令得到不同密钥。加密后还要哈希吗?AEAD 模式已内建完整性校验,无需再叠加哈希;用 CBC 时才需要额外做 MAC。
存储格式建议
// 让 IV 与标签和密文一起存,格式自带版本号与算法标识
{
"v": 1, // 版本号,便于日后升级算法
"alg": "AES-GCM",
"iv": "base64...", // 12 字节随机
"ct": "base64...", // 密文
"tag": "base64..." // 认证标签(部分库与密文合并输出)
}
密钥怎么保存
- 服务端密钥放在密钥管理服务或环境变量中,不要写进代码与仓库;
- 需要长期保存数据时用信封加密:由 KMS 里的主密钥保护每条数据的数据密钥;
- 客户端加密场景,密钥交给密码管理器或系统钥匙串,不要放 localStorage;
- 提前规划轮换:算法升级或密钥疑似泄露时要能平滑迁移——这正是加版本号的原因。
实战案例:三个易错点
- “同一明文每次密文不同,是不是坏了?”没坏。CBC/GCM 每次使用随机 IV,密文自然不同;解密时要带上同样的 IV。
- “改了密文一个字节,解密直接报错”:这是 GCM 等认证加密的正常行为——它检测到篡改并拒绝解密,比 CBC 只返回乱码更安全。
- “生产环境用 ECB 被安全评审打回”:ECB 不用 IV,相同明文块产生相同密文,会泄露图片与数据结构,敏感场景应禁用。
常见问题(FAQ)
密钥怎么安全地传给别人?密钥与密文分开、走不同通道传递(如密钥平台、线下),不要放在同一条消息里。IV 要一起保存吗?要,IV 不是秘密,但解密必须用到,通常随密文一起存。AES-GCM 的 tag 是什么?它是完整性校验值,用于验证密文未被篡改;缺了它就无法校验完整性。密钥泄漏了怎么办?立即轮换密钥并重新加密数据——对称加密没有“改密码”这种便利。
nonce 复用的后果
- GCM 下灾难性:同一密钥下 nonce 复用会让攻击者恢复认证密钥并伪造消息,这比单纯的明文泄露更严重;
- CBC 下可推断明文:相同 IV 与相同明文前缀会得到相同密文块,从而暴露内容结构(ECB 的“企鹅图”就是极端例子);
- 生成方式:优先用随机 96 位 nonce 并限制单密钥加密次数,或改用计数器式 nonce 并确保不会回绕;
- 密钥轮换:接近次数上限时必须换密钥,不要无限制地用同一密钥加密。