📑 本页目录(点开跳转)
19 · 密钥管理
⏱ 26 分钟 | ⭐ 真实系统 90% 的事故出在这一章
🎯 一句话
算法几乎从不被攻破,密钥管理天天出事。 Git 里的硬编码密钥、永不轮换的凭据、日志里的明文 token —— 这才是密码系统真实的死因。
🗝️ 一、密钥的分层
根密钥 / 主密钥(KEK) ← 存在 HSM / KMS 里,【永不导出】⭐
│ 加密
▼
数据加密密钥(DEK) ← 加密实际数据,可以有很多个
│ 派生
▼
会话密钥 ← 每次连接新生成,用完就删
🔑 这个分层叫「信封加密」,是云上的标准做法: 数据用 DEK 加密,DEK 用 KEK 加密后和数据存在一起。
好处: ├─ 轮换 KEK 只需重新加密那些很短的 DEK,不用重新加密 TB 级数据 ⭐ ├─ KEK 永不离开 HSM └─ 不同数据可以用不同 DEK,隔离爆炸半径
🌱 二、密钥派生:一个主密钥生出很多个
❌ 错误:多个用途共用同一个密钥
→ 一处泄露全线崩溃
→ 某些组合还会互相打穿(第 17 章的密钥复用)
✅ 正确:用 KDF 从主密钥派生
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
def derive(master, purpose: bytes, salt: bytes = None):
return HKDF(algorithm=hashes.SHA256(), length=32,
salt=salt, info=purpose).derive(master) # ⭐ info 做域分隔
enc_key = derive(master, b"myapp/v1/encryption")
mac_key = derive(master, b"myapp/v1/authentication") # ⭐ 不同用途不同密钥
HKDF 的两个阶段:
Extract:把一个【分布不均匀】的输入(如 DH 的 g^ab)
压成一个均匀的伪随机密钥 ⭐
Expand: 从它扩展出任意多个、任意长度的子密钥
⚠️ 别把用户口令直接喂给 HKDF: 口令熵太低,HKDF 很快,等于没保护。 口令必须走 Argon2id / scrypt(第 9 章)—— 那是慢的、抗 GPU 的。
高熵输入(DH 结果、随机主密钥) → HKDF ✅ 低熵输入(用户口令) → Argon2id ✅
🔄 三、轮换与生命周期
生成 → 分发 → 使用 → 轮换 → 归档 → 销毁
▲
└── 这一步最常被跳过 ⭐
为什么必须轮换:
| 理由 | 说明 |
|---|---|
| 限制爆炸半径 | 密钥泄露只影响它服役那段时间的数据 |
| 限制加密数据量 | 生日界!同一密钥加密的分组数不能接近 2^(n/2)(第 6 章)⭐ |
| 人员流动 | 离职的人可能见过旧密钥 |
| 合规要求 | PCI-DSS 等标准强制要求 |
轮换的工程难点:
⭐ 关键:轮换必须支持【新旧密钥共存】的过渡期
① 新数据用新密钥加密
② 旧数据仍能用旧密钥解密
③ 后台逐步把旧数据重加密
④ 全部迁移完成后才销毁旧密钥
💡 所以密文里必须带【密钥版本号】⭐
形如:v2 | nonce | ciphertext | tag
💥 不带版本号的后果:轮换那天所有旧数据全部解不开 —— 这是一个非常常见的生产事故。
💥 四、真实世界的密钥泄露方式(按频率排序)
① 代码仓库里的硬编码密钥 ⭐⭐ ← 头号杀手
GitHub 上每天泄露【数千】个有效凭据
② 日志 / 错误信息里打印了密钥或 token
③ 配置文件被打进容器镜像 / 前端 bundle
④ CI/CD 的环境变量泄露到构建日志
⑤ 备份文件未加密
⑥ 内部人员
⑦ ……很久之后才是"算法被攻破" 💀
防御清单:
| 措施 | 做什么 |
|---|---|
| 密钥扫描 ⭐ | pre-commit hook + CI 扫描(gitleaks、truffleHog) |
| 集中管理 | Vault / AWS KMS / Azure Key Vault,代码里只有引用不含密钥 |
| 短期凭据 ⭐ | 用 IAM 角色 / OIDC 换取临时凭据,根本不存长期密钥 |
| 日志脱敏 | 结构化日志 + 敏感字段白名单 |
| 泄露即轮换 | ⚠️ 从 git 历史里删掉不等于没泄露——必须假设已泄露并轮换 ⭐ |
🔑 最后一条最重要,也最常被误解: 很多人发现密钥进了 git 之后,
git filter-branch删掉历史就以为没事了。 但那个 commit 可能已经被 clone、被 fork、被爬虫抓走了。 唯一正确的反应是:立刻轮换那把密钥。
🏦 五、HSM 与 KMS
HSM(硬件安全模块):
├─ 密钥在专用硬件里生成,【永不以明文离开】⭐
├─ 你只能发"用这把密钥签这个"的请求,拿到结果
├─ 物理防拆(拆开就自毁)
└─ 用于:根 CA、支付系统、证书签发
KMS(云密钥管理服务):
├─ AWS KMS / GCP KMS / Azure Key Vault
├─ 提供 生成/存储/轮换/审计/访问控制
└─ ⚠️ 代价:你必须【信任云厂商】
云 KMS 的信任边界问题:
⭐ 你把密钥交给云,就是把"最终解密能力"交给了云
缓解方案:
├─ BYOK(自带密钥):你生成密钥再导入
├─ HYOK(自持密钥):密钥留在你自己的 HSM 里,云只调用
└─ 客户端加密:数据【上传前】就在你这边加密,云只存密文 ⭐
🤝 六、多方持有一个密钥
| 方案 | 做法 | 用在哪 |
|---|---|---|
| Shamir 秘密共享 ⭐ | 把密钥拆成 n 份,任意 k 份能恢复,k−1 份得不到任何信息 | 根 CA 密钥、冷钱包 |
| 门限签名 | k 个人协作签名,私钥从未被完整重建过 ⭐ | 机构级加密货币托管 |
| 多重签名 | n 个独立签名,需要 k 个才算数 | 区块链多签钱包 |
📐 Shamir 秘密共享怎么做到的(想看再点)
核心思路:k 个点唯一确定一条 k−1 次多项式。
- 把秘密 S 作为常数项,随机选其余系数: $f(x) = S + a_1x + a_2x^2 + \cdots + a_{k-1}x^{k-1}$
- 每个人拿到一个点 $(i, f(i))$
- k 个点 → 拉格朗日插值还原 f → f(0) = S ✅
- k−1 个点 → 剩下的那个自由度让 f(0) 可以是任何值 → 信息论意义上零泄露 ⭐
💡 注意它和第 3 章 OTP 一样是信息论安全的 —— 不依赖任何计算假设。
💡 门限签名 vs 多重签名的关键区别: 多签是"n 个签名都放链上"(占空间、暴露了策略); 门限签名产出的是一个普通签名,外界看不出它是多方协作的 ⭐。
🔗 和站内其他章的关系
| 相关的地方 | 和这一章的关系 |
|---|---|
| 第 15 章 前向保密 | 会话密钥用完即删 |
| 第 11 章 加密和 MAC 用不同密钥 | 用 HKDF 派生 |
| 第 6 章 生日界 | 密钥轮换的量化依据 ⭐ |
| 第 9 章 Argon2 | 低熵输入必须用它 |
| 第 4 章 熵源 | 密钥生成的前提 |
| 智能体工程 15 · 安全:沙箱与提示注入 | ⭐ 那边的硬边界「凭据不进沙箱」,正是本章 KEK/DEK 分层在 Agent 架构上的落地:让被提示注入骗到的模型即使照做,也够不着密钥本身 |
| 模型上线之后 · 合规审计与模型卡 | 轮换周期、谁能取密钥、取过的记录——这些全是审计要查的条目;密钥管理做不好,合规那一关直接过不去 ⭐ |
| Claude 资料库 · 触达生产系统 | 「别自建密钥管理」的工程落地版:用托管 Vault 存取和刷新 token——和本章 HSM / KMS 那一节是同一个结论 |
✅ 检查点
- 什么是信封加密?它有哪三个好处?
- 为什么不同用途必须用不同密钥?怎么做到?
- HKDF 的两个阶段分别做什么?
- 为什么用户口令不能直接喂给 HKDF?该用什么?
- 必须轮换密钥的四个理由?
- 轮换的工程关键点是什么?不做会怎样?
- 真实世界密钥泄露的头号原因是什么?
- 密钥进了 git 之后,删掉历史就够了吗?
- Shamir 秘密共享的原理是什么?为什么 k−1 份得不到任何信息?
- 门限签名和多重签名的关键区别?
👀 答案
- 数据用 DEK 加密,DEK 用 KEK 加密后和数据存在一起。好处:①轮换 KEK 只需重新加密很短的 DEK,不用重加密 TB 级数据 ②KEK 永不离开 HSM ③不同数据用不同 DEK,隔离爆炸半径。
- 因为一处泄露会全线崩溃,某些组合还会互相打穿。用 HKDF 派生,把用途字符串放进
info参数做域分隔。 - Extract:把分布不均匀的输入(如 DH 的 g^ab)压成均匀的伪随机密钥;Expand:从它扩展出任意多个任意长度的子密钥。
- 因为口令熵太低,而 HKDF 很快,等于没保护。必须用 Argon2id / scrypt——慢的、抗 GPU 的。规则:高熵输入走 HKDF,低熵输入走 Argon2id。
- ①限制爆炸半径 ②限制同一密钥加密的数据量(生日界)③人员流动 ④合规要求。
- 必须支持新旧密钥共存的过渡期:新数据用新密钥,旧数据仍能解密,后台逐步重加密,全部迁移完才销毁旧密钥。所以密文必须带密钥版本号。不做的话轮换那天所有旧数据全部解不开。
- 代码仓库里的硬编码密钥。GitHub 上每天泄露数千个有效凭据。
- 不够。那个 commit 可能已被 clone、fork、爬虫抓走。唯一正确的反应是立刻轮换那把密钥。
- k 个点唯一确定一条 k−1 次多项式。秘密作常数项,每人拿一个点,k 个点插值还原后 f(0) 就是秘密。k−1 个点时剩下的自由度让 f(0) 可以是任何值,所以是信息论意义上零泄露(和 OTP 一样不依赖计算假设)。
- 多签是"n 个签名都放链上"(占空间、暴露策略);门限签名产出一个普通签名,外界看不出它是多方协作的,且私钥从未被完整重建过。
🛑 可以停在这里
⚡ 走神救援
⭐算法几乎从不被攻破,密钥管理天天出事。分层(信封加密):KEK 在 HSM 里永不导出 → 加密 DEK → 派生会话密钥;⭐好处是轮换 KEK 只需重加密很短的 DEK,不用动 TB 级数据。派生:用 HKDF,把用途放
info做域分隔(不同用途必须不同密钥);两阶段 Extract(把 DH 的 g^ab 这类不均匀输入压成均匀密钥)+ Expand。⚠️⭐用户口令不能喂 HKDF(熵太低且 HKDF 快)——低熵走 Argon2id,高熵走 HKDF。轮换四个理由:限制爆炸半径、⭐限制同一密钥的加密数据量(生日界)、人员流动、合规;⭐工程关键是支持新旧密钥共存的过渡期 → 密文必须带版本号(v2|nonce|ct|tag),💥不带的话轮换那天旧数据全部解不开。⭐真实泄露方式按频率:①代码仓库硬编码(头号杀手,GitHub 每天泄露数千个有效凭据)②日志打印 token ③配置进镜像/前端bundle ④CI 环境变量 ⑤备份未加密 ⑥内部人员 ⑦……很久之后才是算法被破。防御:gitleaks 扫描、Vault/KMS 集中管理、⭐短期凭据(OIDC 换临时凭据,根本不存长期密钥)、日志脱敏;⚠️⭐从 git 历史删掉 ≠ 没泄露——commit 可能已被 clone/fork/爬虫抓走,唯一正确反应是立刻轮换。HSM:密钥永不明文离开硬件,物理防拆;云 KMS 方便但你必须信任云厂商 → 缓解用 BYOK/HYOK/⭐客户端加密(上传前就加密,云只存密文)。多方持有:⭐Shamir 秘密共享(k 个点唯一确定 k−1 次多项式,秘密作常数项;k−1 份时 f(0) 可以是任何值 ⟹ 信息论零泄露,和 OTP 一样)、门限签名(私钥从未被完整重建,且产出普通签名外界看不出是多方协作,优于多签)。
下一节 👉 20-区块链.md