文章目录
- 引言
- 一、先分清:加密只解决机密性
- 二、对称加密为什么是主力
- 三、算法选型:默认 AEAD,不要裸用底层模式
- 四、ECB 为什么必须避开
- 五、IV 和 nonce:通常不保密,但必须满足模式要求
- 六、防重放:合法消息也可能被再次利用
- 七、解密端攻击:错误信息也是接口
- 八、加密端攻击:攻击者也可能让系统帮他加密
- 九、防调包:让密文只在正确上下文中有效
- 十、AEAD 不是免死金牌
- 十一、随机数:安全材料只能来自 CSPRNG
- 十二、密钥从哪里来:不要把口令当密钥
- 十三、密钥管理:KMS、信封加密与轮换
- 十四、量子时代:对称密码不是马上失效,但要提高迁移能力
- 十五、一个可落地的加密设计模板
- 结语:正确使用比“用上加密”更重要
- 参考资料

引言
只要系统处理过登录态、支付回调、用户隐私、配置下发、对象存储、备份、设备指令、Webhook 或服务间调用,加密算法就已经参与了业务安全。它不一定出现在一个叫 CryptoService 的模块里,更多时候藏在数据库字段、接口签名、会话密钥、随机 token、密文 blob 和密钥轮换脚本里。
加密算法最容易制造错觉。代码里调用了 AES.encrypt(),数据看起来是一串随机字符,日志里没有异常,评审时也能说“已经加密了”。但工程事故往往不发生在算法名字上,而发生在使用方式上:ECB 泄露结构,GCM 重复 nonce,只加密不认证,密文没有绑定用户和租户,重放请求没有被拒绝,随机数来自 Math.random(),密钥硬编码在配置文件里,一把密钥从测试环境用到生产环境。
现代密码学工程的重点不是发明算法,而是把成熟算法放在正确的位置。算法公开,密钥保密;优先使用标准库;密文格式可迁移;认证覆盖上下文;随机数来自 CSPRNG;密钥有生命周期;失败路径不泄露细节。把这些边界守住,比追逐“更强”的算法名字更重要。
面向后端、移动端、平台工程、安全工程和技术负责人,最有价值的不是记住算法名称,而是建立一套能落地的判断框架:什么场景该用 AEAD,nonce 怎么保证唯一,密文如何绑定业务上下文,密钥从哪里来、放在哪里、什么时候轮换,未来算法迁移时系统是否还改得动。
一、先分清:加密只解决机密性
加密的核心目标是机密性,也就是让未授权方看不懂数据。密文可以被存储、传输、备份,甚至被攻击者拿到;只要密钥没有泄露,攻击者就无法有效恢复明文。
但加密并不自动解决完整性、身份认证和防重放。
一段密文看不懂,不代表它没被改过。传统 CBC、CTR 等模式只提供加密能力,攻击者可能无法直接读出明文,却可以修改密文并观察系统反应。如果系统解密后继续解析 JSON、返回具体错误、执行部分业务逻辑,就可能被当成攻击接口。
一段密文能被成功解密,也不代表它属于当前用户。攻击者可以把 A 用户的密文复制到 B 用户的记录里,或者把低权限资源的密文移动到高权限接口下。如果加密时没有把用户、租户、资源、协议版本等上下文绑定进去,系统很难区分“合法密文”和“被放错位置的合法密文”。
一次请求的签名正确,也不代表它是新的。攻击者截获一条合法支付请求后原样重放,HMAC、数字签名或 AEAD 标签仍然可能验证通过,因为旧消息本来就是真的。防重放必须把时间戳、nonce、序列号或业务幂等键纳入协议。
所以,设计加密方案前要先拆清楚目标:
| 安全目标 | 该解决的问题 | 常用手段 |
|---|---|---|
| 机密性 | 谁能看 | AES-GCM、ChaCha20-Poly1305、TLS、信封加密 |
| 完整性 | 内容是否被改 | HMAC、AEAD tag、数字签名 |
| 身份认证 | 谁发的 | HMAC、mTLS、数字签名、证书 |
| 防重放 | 消息是否新鲜 | 时间戳、nonce、序列号、幂等键 |
| 上下文绑定 | 数据在哪些边界内有效 | AEAD AAD、签名规范化字段 |
| 密钥生命周期 | 密钥如何生成、保存、轮换、吊销 | KMS、HSM、key id、审计、版本化密文 |
很多系统的问题不在于“没有加密”,而在于把“加密”当成了所有安全目标的总称。
二、对称加密为什么是主力
对称加密的特点很直接:加密和解密使用同一把密钥。发送方用密钥把明文变成密文,接收方用同一把密钥把密文还原成明文。
它的优势是速度快、密文膨胀小、实现成熟,适合处理大量数据。数据库字段加密、文件加密、备份加密、对象存储加密、TLS 会话数据加密,主要都靠对称加密完成。真实协议通常用非对称算法解决身份认证或密钥协商,再用协商出的对称密钥保护业务数据。
它的难点也很明显:通信双方必须安全地拥有同一把密钥。参与者越多,密钥分发越复杂。5 个参与者两两通信需要 10 把共享密钥,10 个参与者需要 45 把。规模继续扩大后,手工维护密钥矩阵很快失控,必须引入 KMS、会话密钥协商、证书体系或中心化密钥服务。
对称加密的安全上限由密钥决定。算法应当公开、标准化、经过长期分析;秘密只应该放在密钥里。自研算法、私有混淆、Base64、字符反转、固定异或、把字段“打乱一下”,都不是现代密码学意义上的安全方案。
在工程实践中,新系统应优先使用认证加密,也就是 AEAD。只加密不认证的方案已经不适合作为默认选择。
三、算法选型:默认 AEAD,不要裸用底层模式
AEAD 是 Authenticated Encryption with Associated Data,中文通常叫 带关联数据的认证加密。
它同时解决两件事:
- 机密性:别人看不懂密文内容。
- 完整性与认证:别人不能偷偷篡改密文;一旦改了,解密时会验证失败。
其中 Associated Data,关联数据 指的是:不需要加密、但必须防篡改的数据。比如:
密文内容:订单金额、用户信息
关联数据:用户 ID、接口路径、协议版本、订单号
关联数据不会被隐藏,但会参与认证校验。攻击者如果改了关联数据,解密也会失败。
常见 AEAD 算法包括:
- AES-GCM
- ChaCha20-Poly1305
- AES-CCM
简单说:AEAD = 加密 + 防篡改 + 可绑定上下文。新系统里做对称加密,通常优先选 AEAD,而不
是单独使用 AES-CBC、AES-CTR 这类裸加密模式。
开发者选算法时容易陷入两个误区:只看位数,或者只看性能。
AES-256 听起来比 AES-128 强,但如果 nonce 重复、密钥泄露、认证缺失,AES-256 一样会失败。某个模式跑分很快,也不代表它适合业务协议。算法选型要同时考虑安全性、生态、硬件支持、误用风险和迁移成本。
1. AES-GCM
AES-GCM 是当前服务端系统最常见的 AEAD 选择之一。它同时提供加密和认证,支持 AAD (Additional Authenticated Data,附加认证数据),生态成熟,硬件加速广泛。对多数后端、云服务、数据库字段加密和内部协议来说,AES-GCM 是很稳的默认选项。
它最大的工程风险是 nonce 复用。同一把密钥下,GCM nonce 一旦重复,可能泄露明文关系并破坏认证强度。密钥长度无法弥补这个错误。
2. ChaCha20-Poly1305
ChaCha20 是序列密码,通常和 Poly1305 组合成 ChaCha20-Poly1305。它也是 AEAD,常见于 TLS、移动端和跨平台网络协议。在没有 AES 硬件加速的设备上,ChaCha20-Poly1305 的软件性能很稳定。
它同样要求同一密钥下 nonce 不重复。不要因为它不是 AES,就放松 nonce 管理。
3. AES-CCM
AES-CCM 常见于部分受限环境和无线协议。它也是认证加密模式,但使用体验和性能特点不一定适合通用后端服务。除非协议或设备生态要求,否则一般不需要把它作为新业务默认选项。
4. Ascon-AEAD128
轻量密码正在成为 IoT、嵌入式、低功耗传感器等场景的重要方向。NIST 在 2025 年发布 SP 800-232,正式标准化 Ascon 系列轻量密码原语,其中包括 AEAD、哈希和可扩展输出函数。对资源受限设备来说,Ascon-AEAD128 是值得关注的新标准方向。
普通服务端业务不需要为了“新”而替换 AES-GCM 或 ChaCha20-Poly1305;但如果你在做受限设备、安全芯片、传感器网络或低功耗通信协议,就应该把 Ascon 纳入技术评估。
5. 不再用于新系统的选择
DES、3DES、RC4 不应进入新系统。ECB 不应进入任何新的安全设计。CBC、CTR 不是绝对不能碰,但它们只是加密模式,不是完整安全方案;必须配合认证、正确 IV、严格填充处理和统一错误响应。除非兼容历史协议,否则没有必要裸用这些模式。
一个务实的默认选择是:
- 服务端和云环境:AES-256-GCM 或 AES-128-GCM。
- 移动端和跨平台通信:ChaCha20-Poly1305 或 AES-GCM。
- 受限设备和标准约束协议:AES-CCM 或 Ascon-AEAD128。
- 历史系统兼容:保留 CBC/CTR 时必须使用 encrypt-then-MAC,并计划迁移。
四、ECB 为什么必须避开
ECB 是最容易理解的分组密码模式:把明文分成固定大小的数据块,每个块独立加密。规则简单,问题也致命:相同明文块在相同密钥下会产生相同密文块。
这意味着 ECB 会泄露结构。图片用 ECB 加密后,轮廓可能仍然看得出来;业务数据也是一样。固定模板、重复字段、相同状态码、相同地区、相同手机号、相同订单类型,都可能在密文中留下可观察模式。
攻击者不一定需要完整解密。知道哪些记录相同,哪些字段重复,哪些块发生了变化,就足够做频率分析、关联用户行为或推断业务状态。
数据库字段加密尤其容易掉进这个坑。有人为了支持等值查询,选择用 ECB 加密手机号、身份证号或邮箱。结果同一个手机号永远得到同一个密文,攻击者拿到数据库后可以按出现频率、外部字典、已知样本进行关联。低熵字段本来就容易枚举,ECB 还额外暴露了相等关系。
“给明文加个随机前缀再用 ECB”也不是好方案。你实际上是在手工发明加密模式,很容易在长度、对齐、重复块、认证缺失上继续出错。标准 AEAD 已经解决了这些问题,没有必要绕远路。
如果业务需要查询加密字段,应重新设计边界:使用盲索引、拆分敏感字段、限制查询条件、引入专门的可搜索加密方案,或者把查询能力移到受控服务里。不要用 ECB 换取索引便利。
五、IV 和 nonce:通常不保密,但必须满足模式要求
IV 和 nonce 经常被误解成“另一把密钥”。大多数场景下,它们不需要保密,甚至要和密文一起保存;真正重要的是满足算法要求。
如果相同明文在相同密钥下总是得到相同密文,攻击者就能观察重复模式。IV/nonce 的作用是给每次加密引入变化量,让同一明文在不同加密实例中得到不同密文。
不同模式要求不同:
- CBC 通常需要不可预测 IV。
- CTR、GCM、ChaCha20-Poly1305 更强调同一密钥下 nonce 唯一。
- 某些协议会规定 nonce 的长度、结构和计数方式,不能自行发挥。
GCM 的 nonce 管理尤其关键。同一密钥下重复 nonce 是灾难性错误。不要用时间戳、用户 ID、短随机数、订单号、数据库自增 ID 直接当 nonce。它们可能重复、可预测、跨实例冲突,或者在系统回滚后复用。
高吞吐和分布式系统更适合结构化 nonce,例如:
nonce = instance_prefix || monotonic_counter
或者:
nonce = session_random_prefix || message_sequence
关键是保证同一密钥作用域内唯一。instance_prefix 要避免多节点冲突,counter 要防止进程重启、快照恢复、容器回滚后复用旧值。随机 nonce 不是不能用,但长度必须足够,随机源必须来自 CSPRNG,并且要限制单把密钥下的加密次数。
密文格式里应保存 nonce。不要把 nonce 藏在外部状态里,否则数据迁移、备份恢复、异地容灾都会变脆弱。
一个可维护的密文记录通常至少包含:
version || algorithm || key_id || nonce || ciphertext || tag
如果密钥来自口令派生,还要保存 salt、KDF 名称和参数。如果 AAD 不是从业务上下文稳定推导出来,也要保存 AAD 标识或可重建信息。
六、防重放:合法消息也可能被再次利用
重放攻击的思路很简单:攻击者不破解密钥,不修改内容,只复制一条合法消息再发一次。
支付扣款、订单确认、优惠券核销、设备开门、登录验证码、Webhook 回调、转账请求、配置下发,都可能受到重放攻击影响。HMAC 能证明消息没被改,数字签名能证明消息来自私钥持有者,AEAD 能证明密文和上下文匹配,但它们都不自动证明消息是新的。
防重放必须把时间或顺序纳入协议。
常用手段有四类。
第一,时间戳。请求带上时间戳,并把时间戳纳入签名。服务端只接受短窗口内的请求,例如 5 分钟。它简单有效,但依赖时钟同步,也不能阻止窗口内重复。
第二,nonce。每个请求带唯一随机数或唯一 ID,服务端记录已使用 nonce。重复出现就拒绝。它防护更强,但需要存储、过期清理和分布式一致性。
第三,序列号。客户端和服务端维护单调递增的消息编号,旧编号直接拒绝。它适合长连接、设备通信和会话协议,但需要处理乱序、重试和窗口滑动。
第四,业务幂等键。支付、订单、库存等业务通常需要 request_id、transaction_id 或幂等表,确保同一业务操作只执行一次。
防重放字段必须进入认证范围。否则攻击者可以把旧请求里的时间戳或 nonce 改掉。
signature = HMAC-SHA256(
key,
method || path || timestamp || nonce || body_hash
)
服务端处理顺序也要谨慎:先做格式和时间窗口检查,再检查 nonce 或幂等键,再验签,最后执行业务。关键交易的幂等状态要落库,不能只放本地内存。多个节点各自维护本地 nonce 集合,会给跨节点重放留下空间。
七、解密端攻击:错误信息也是接口
解密端攻击把系统当作一个黑盒探针。攻击者不断提交构造过的密文,观察状态码、错误信息、响应时间、日志 ID 或业务行为,从差异中推断内部状态。
经典 padding oracle 就是这类问题。使用 CBC 时,如果填充错误和认证错误返回不同响应,攻击者可以反复修改密文块,通过错误差异逐步恢复明文。系统没有直接把明文返回给攻击者,但它的反应泄露了足够多的信息。
常见危险流程是:
接收密文 -> 解密 -> 解析 JSON -> 检查字段 -> 查数据库 -> 返回具体错误
每一步都可能暴露差异。padding invalid、JSON parse error、user_id missing、tenant mismatch、key not found、tag mismatch,这些错误对开发者很友好,对攻击者也很友好。
现代设计应尽量使用 AEAD:认证标签验证失败时,整体失败,不释放明文,不进入解析流程。
如果必须使用“加密 + MAC”的组合,应采用 encrypt-then-MAC:
mac = HMAC(mac_key, version || iv || ciphertext || aad)
packet = version || key_id || iv || ciphertext || mac
解密时先验证 MAC,验证通过后再解密。不要先解密再验 MAC,不要只 MAC 明文,不要遗漏 IV、版本号、算法标识和上下文。
对外错误应该收敛成一种失败:
400 invalid encrypted payload
内部日志可以记录细节,但不要记录明文、密钥、完整 token 或可被滥用的中间值。失败路径的响应时间也要避免明显分叉,尤其是高敏接口。
八、加密端攻击:攻击者也可能让系统帮他加密
加密端攻击关注另一侧:如果攻击者能控制部分明文,并观察对应密文,会不会从长度、重复模式、压缩效果或输出差异里推断秘密?
这并不罕见。Web 应用里,攻击者能控制用户名、搜索词、备注、请求头、表单字段、文件名、跳转参数。如果这些可控输入和服务端秘密一起进入压缩、加密或哈希流程,就可能泄露线索。
典型风险包括:
- 把
secret || user_input拼在一起加密后返回密文。 - 压缩后再加密,且攻击者能观察输出长度。
- 确定性加密低熵字段,暴露相等关系和频率。
- 给外部用户提供无约束的“加密 oracle”。
- 同一密钥跨协议复用,让一个协议的可控输入影响另一个协议的安全边界。
AEAD 能防篡改,但不能消除所有侧信道。密文长度通常仍会暴露明文长度范围。对高敏场景,可能需要固定长度填充、分桶、分块传输或隐藏访问模式。
这里的工程原则是:不要让攻击者控制的数据和秘密在同一个可观察函数里紧密混合。确实需要拼接时,要明确协议结构、长度编码、域分离和认证边界。
九、防调包:让密文只在正确上下文中有效
数据被调包,本质是完整性和上下文绑定失败。攻击者不需要看懂密文,只要能把一段合法数据移动到错误位置,就可能破坏业务。
典型例子:
- 把 A 用户的加密地址复制到 B 用户名下。
- 把普通租户的密文放到高级租户记录里。
- 把旧版本 token 放进新版本解析流程。
- 把低权限接口返回的加密字段提交给高权限接口。
- 把测试环境密文带到生产环境。
单纯哈希挡不住主动攻击。攻击者如果能替换数据,也能重新计算没有密钥的哈希。防调包需要带密钥的认证机制,例如 HMAC 或 AEAD tag。
AEAD 的 AAD 适合做上下文绑定。AAD 不会被加密,但会参与认证。可以把“不怕被看见但不能被改”的上下文放进去:
aad = version || environment || tenant_id || user_id || resource_id || purpose
ciphertext, tag = AEAD_Encrypt(key, nonce, plaintext, aad)
解密时必须提供相同 AAD。密文被复制到其他用户、租户、资源或环境下,认证会失败。
如果使用 HMAC,也要覆盖上下文:
mac = HMAC-SHA256(
key,
version || tenant_id || user_id || resource_id || body_hash
)
规范化必须提前写清楚。字段顺序、编码方式、大小写、空白字符、重复字段、数组排序、JSON 序列化规则,都要固定。否则客户端和服务端可能对“同一段数据”有不同理解,攻击者就能利用解析差异。
防调包的目标不是“多加一个签名字段”,而是让这份数据只在被授权的上下文中有效。
十、AEAD 不是免死金牌
AEAD 是现代应用加密的首选接口,因为它把机密性、完整性和关联数据认证组合进一个标准 API,减少手工拼错的概率。但 AEAD 仍然会被错误使用。
最严重的问题是 nonce 重复。AES-GCM 和 ChaCha20-Poly1305 都要求同一密钥下 nonce 不重复。分布式系统、容器重启、虚拟机快照恢复、计数器回滚、多实例前缀冲突,都是实际风险。
第二个问题是 AAD 漏掉关键上下文。只加密正文,不绑定租户、用户、资源、版本和用途,密文仍可能被复制到错误位置后成功解密。
第三个问题是错误处理不统一。认证失败、版本错误、key id 不存在、tag 错误、nonce 长度错误,对外都应该是统一失败。不要在失败后尝试解析明文,也不要为了兼容老数据反复尝试多个密钥并暴露差异。
第四个问题是密钥复用。同一把密钥不要同时用于 AES-GCM、HMAC、测试数据、生产数据、多个业务域。需要多个用途时,用 HKDF 从高熵主密钥派生子密钥,并在 info 中写清用途:
encrypt_key = HKDF(master_key, info="encrypt:user-profile:v1")
mac_key = HKDF(master_key, info="mac:webhook:v1")
第五个问题是忽略消息数量和数据量上限。某些 AEAD 模式对单个密钥可安全处理的消息数量、数据量和标签长度有约束。高吞吐日志、埋点、事件流和网关系统不能无限期使用同一把密钥。NIST 已启动对 GCM/GMAC 指南 SP 800-38D 的修订工作,关注点之一正是现代高吞吐场景下 GCM 的使用边界。
一个稳妥的 AEAD 使用模板是:
packet = {
version: 1,
alg: "AES-256-GCM",
key_id: "kms-key-2026-01",
nonce: base64url(nonce),
aad_context: "tenant/user/resource/purpose derived by protocol",
ciphertext: base64url(ciphertext),
tag: base64url(tag)
}
实际系统可以用二进制、JSON、Protobuf 或紧凑字符串格式,但字段语义必须稳定,解密逻辑必须严格。
十一、随机数:安全材料只能来自 CSPRNG
密码学系统离不开随机数。密钥、nonce、salt、token、验证码、会话 ID、挑战值,都依赖随机性。
普通随机数不安全。Math.random()、线性同余、时间戳、雪花 ID、自增 ID、简单 UUID 变体,都不适合生成密钥、token 或安全 nonce。它们可能满足统计分布,却不满足不可预测性。攻击者只要推断种子、时间窗口或内部状态,就可能预测后续输出。
密码学需要 CSPRNG,也就是密码学安全伪随机数生成器。它要求攻击者即使看到一部分输出,也难以推断内部状态和未来输出。工程上不要自己实现随机数生成器,也不要自己播种,直接使用操作系统或语言标准库提供的安全接口:
- Java:
SecureRandom - Go:
crypto/rand - Node.js:
crypto.randomBytes、crypto.webcrypto.getRandomValues - Python:
secrets、os.urandom - Linux 底层:
getrandom() - 浏览器:
crypto.getRandomValues
还要区分随机和唯一。密钥必须不可预测并保密;salt 通常需要随机且唯一,但不需要保密;nonce 经常只要求唯一,不一定要求保密。高频加密系统如果只靠随机 nonce,要评估碰撞概率和单 key 消息上限;结构化 nonce 往往更可控。
容器、虚拟机和嵌入式设备还要关注熵源质量。系统启动早期熵不足时,安全随机接口可能阻塞;虚拟机克隆、容器快照、低成本 IoT 设备冷启动,也可能导致随机状态异常。密钥生成、证书生成、设备初始化这些环节尤其要谨慎。
十二、密钥从哪里来:不要把口令当密钥
一个合格的对称密钥至少要满足四点:长度正确、强度足够、不可预测、用途明确。
最理想的密钥来自 CSPRNG 或 KMS。AES-128 需要 128 位密钥,AES-256 需要 256 位密钥。生成后应以二进制形式保存或交给 KMS 管理,不要手工转成容易丢失熵的字符串,也不要让开发者复制粘贴到配置文件里。
用户口令不能直接作为加密密钥。口令通常熵很低,攻击者拿到密文或派生结果后可以离线枚举。也不要简单做:
key = SHA256(password)
正确做法是使用口令派生函数:
- PBKDF2:兼容性好,传统系统和合规环境常见。
- bcrypt:常用于口令存储。
- scrypt:引入内存成本,提升批量破解成本。
- Argon2id:现代口令哈希的优先选择之一,具备内存困难特性。
盐值必须每个用户或每份数据不同,并和结果一起保存。盐值不需要保密,它的作用是防止相同口令产生相同派生结果,抵抗预计算表。pepper 是额外的服务端秘密,通常放在 KMS、HSM 或受控环境中,用来提升攻击者仅拿到数据库时的破解成本。
派生参数要版本化:
algorithm = "argon2id"
params = "m=65536,t=3,p=1"
salt = random_bytes(16)
version = 2
用户登录或解密成功后,如果发现参数落后,可以在后台升级派生参数。不要把口令派生参数写死十年不变。
当系统已有一个高熵主密钥,需要派生多个用途的子密钥时,使用 HKDF。info 字段要写清用途、业务域和版本,完成域分离。不要把同一把密钥同时用于加密、认证、测试和生产。
十三、密钥管理:KMS、信封加密与轮换
密钥管理通常比算法选择更难。算法可以查标准,库可以直接调用;密钥要贯穿生成、分发、存储、使用、轮换、吊销、备份和销毁。
有些密钥适合即用即弃。TLS 会话密钥就是典型例子:会话建立时协商,用完丢弃。它的暴露窗口短,也更容易实现前向保密。某些本地文件加密也可以用用户口令临时派生密钥,使用后从内存中清除,不落盘保存。
有些密钥必须留存。数据库字段加密、对象存储加密、备份加密都需要多年后能解密历史数据。留存密钥必须有严格保护:KMS、HSM、访问控制、审计、最小权限、自动轮换、密钥版本和恢复流程。
不要把密钥放在源码、镜像、普通配置文件、Wiki、聊天记录、工单评论、前端包或日志里。密钥读取应尽量集中在受控组件中,业务服务只拿到短期可用的明文密钥或受限加解密能力。
成熟系统常用信封加密:
data_key = random 256-bit key
ciphertext = AEAD_Encrypt(data_key, plaintext, aad)
wrapped_data_key = KMS_Encrypt(master_key, data_key)
record = key_id || wrapped_data_key || nonce || ciphertext || tag
数据密钥加密具体数据,主密钥只负责加密数据密钥。这样主密钥不直接处理大量业务数据。轮换主密钥时,通常只需要重新包裹数据密钥,不必重写所有密文。
密钥轮换要提前设计。密文里必须有 key_id,解密服务能根据 key_id 找到旧密钥;新写入使用新密钥,旧数据可按访问时重加密或后台批处理迁移。密钥吊销也要区分“停止新加密”“禁止解密”“计划销毁”几个阶段,不能一刀切导致历史数据不可恢复。
一份密钥管理检查清单至少包括:
- 每把密钥是否有 owner 和用途说明?
- 哪些数据由哪把密钥保护,能否追踪?
- 能否撤销单个租户、合作方或业务域的密钥?
- 轮换后历史数据如何解密?
- KMS 权限是否过宽?
- 生产和测试密钥是否隔离?
- 日志、异常、监控和审计中是否可能泄露密钥材料?
- 备份是否包含解密所需材料,恢复流程是否演练过?
密钥管理的目标不是“藏起来”这么简单,而是让密钥在整个生命周期中可控、可审计、可轮换、可恢复。
十四、量子时代:对称密码不是马上失效,但要提高迁移能力
量子计算对密码学的影响不能用“马上全换”或“完全不用管”概括。
对称密码受 Grover 算法影响,暴力搜索成本可以粗略理解为下降到平方根级别。因此,长期保密和高价值数据更适合使用 256 位对称密钥。AES-128 仍然不是今天普通在线业务的主要短板,但面向十年以上保密周期的数据,AES-256 是更保守的选择。
公钥密码面对的挑战更直接。RSA、有限域 Diffie-Hellman、椭圆曲线 Diffie-Hellman、ECDSA 等经典公钥算法,在成熟大规模量子计算机和 Shor 算法面前会面临根本性风险。密钥交换和数字签名是后量子迁移重点。
NIST 已在 2024 年发布首批后量子密码 FIPS 标准:
- FIPS 203:ML-KEM,用于密钥封装。
- FIPS 204:ML-DSA,用于数字签名。
- FIPS 205:SLH-DSA,用于基于哈希的数字签名。
这并不意味着所有系统今天就要替换到后量子算法。更现实的工作是建立密码敏捷性:
- 协议里保留算法标识和版本号。
- 密文格式保存
alg、key_id、nonce、tag 和 KDF 参数。 - 证书、签名、密钥交换支持灰度迁移。
- 服务端能同时支持新旧套件,并能通过配置禁用风险算法。
- 盘点数据保密周期,优先处理“先收集、后解密”风险高的数据。
最糟糕的状态不是暂时还在用经典算法,而是系统里到处都是无法识别算法、无法追踪密钥、无法批量迁移的历史密文。量子时代真正考验的是迁移能力。
十五、一个可落地的加密设计模板
假设要设计一个用户敏感资料加密方案,例如保存身份证号、地址、私密备注。一个较稳妥的设计可以这样落地。
第一,使用 AEAD:
alg = AES-256-GCM
第二,每条记录使用唯一 nonce:
nonce = random 96-bit nonce from CSPRNG
如果系统吞吐极高,改为结构化 nonce,并限制单 key 消息量。
第三,把上下文放进 AAD:
aad = "profile:v1" || environment || tenant_id || user_id || field_name
这样密文不能被复制到其他用户、其他租户或其他字段下使用。
第四,使用信封加密:
data_key = KMS.GenerateDataKey(master_key_id)
ciphertext, tag = AES-GCM(data_key, nonce, plaintext, aad)
wrapped_key = KMS.Encrypt(master_key_id, data_key)
第五,保存自描述密文:
{
"v": 1,
"alg": "AES-256-GCM",
"kid": "kms/customer-profile/2026-01",
"nonce": "...",
"wrapped_key": "...",
"ciphertext": "...",
"tag": "..."
}
第六,严格解密:
解析版本 -> 找 key_id -> 重建 AAD -> AEAD 解密 -> 成功才释放明文
任何认证失败都返回统一错误,不尝试部分恢复,不把明文写入日志。
第七,准备轮换:
- 新写入使用新 key id。
- 旧密文按 key id 解密。
- 访问时可重加密到新版本。
- 主密钥轮换优先重包裹数据密钥。
- 高风险密钥泄露时有吊销和批量迁移预案。
这套模板不复杂,却覆盖了多数事故发生的边界:算法、nonce、认证、上下文、密钥、格式、轮换和错误处理。
结语:正确使用比“用上加密”更重要
加密算法本身通常不是业务系统最薄弱的环节。真正危险的是把密码学工具放错位置:把 ECB 当 AES,用随机数解决唯一性却没有碰撞边界,只加密不认证,只签正文不签上下文,只验证签名不防重放,把口令当密钥,把密钥当普通配置。
面向新系统,优先选择成熟库提供的 AEAD;同一密钥下保证 nonce 唯一;用 AAD 绑定业务上下文;用 CSPRNG 生成安全材料;用 KDF 派生用途明确的子密钥;用 KMS 或 HSM 管理留存密钥;在密文格式里保存版本、算法和 key id;把重放、调包、错误侧信道和迁移能力一起纳入设计。
密码学工程的底线很朴素:不要自研算法,不要省略认证,不要复用密钥,不要忽略上下文,不要丢掉迁移路径。做到这些,绝大多数开发者面对的加密问题就已经从“碰运气”变成了可审计、可维护、可演进的工程系统。
参考资料
- NIST FIPS 203:ML-KEM 后量子密钥封装标准,https://csrc.nist.gov/pubs/fips/203/final
- NIST FIPS 204:ML-DSA 后量子数字签名标准,https://csrc.nist.gov/pubs/fips/204/final
- NIST 后量子密码标准发布说明,https://csrc.nist.gov/news/2024/postquantum-cryptography-fips-approved
- NIST SP 800-232:Ascon 轻量密码标准,https://csrc.nist.gov/pubs/sp/800/232/final
- NIST SP 800-38D:GCM/GMAC 模式说明,https://csrc.nist.gov/pubs/sp/800/38/d/final
- NIST 关于修订 SP 800-38D 的说明,https://csrc.nist.gov/news/2024/nist-to-revise-sp-80038d-gcm-and-gmac-modes

1152

被折叠的 条评论
为什么被折叠?



