1.2 哈希、MAC、KDF 与密码存储
安全要塞收到四件外观相似的工具:哈希、MAC、KDF 和密码哈希,情报官要求你们先分清各自保护什么。
哈希函数、消息认证码、密钥派生函数和密码哈希都能输出一串固定长度字节,但它们解决的问题不同。把 SHA-256(password) 当密码存储,或把普通 hash 当消息签名,都是把相似接口误当成相同安全语义。
普通哈希没有秘密
digest = Hash(message)密码学哈希通常追求原像抗性、第二原像抗性和碰撞抗性。它适合内容寻址、完整性指纹和签名协议的组成部分。
但任何人都能重新计算普通 hash。若攻击者能同时替换文件和 digest,SHA-256(file) 不能证明文件来自谁。MD5 与 SHA-1 已不适合碰撞安全场景;新设计通常使用 SHA-256/384、SHA-3 或协议明确规定的现代算法。
MAC 证明共享密钥持有者生成过消息
tag = HMAC(key, message)HMAC 将秘密密钥纳入认证。验证者必须持有同一密钥,所以它适合服务间 webhook、内部协议和数据库字段完整性。
验证时使用库提供的 constant-time compare,避免普通字符串比较泄露第一个不同字节的位置。还要把协议上下文编码进消息:
version || method || path || timestamp || body_digest字段需要无歧义长度或规范化编码,否则 ab|c 与 a|bc 一类拼接歧义会破坏协议。为防重放,还要校验时间窗口和一次性 ID;合法 HMAC 只证明消息没被改,不证明它是第一次到达。
KDF 用于派生相互隔离的子密钥
不要让一个主密钥同时用于加密、MAC、token 签名和数据库字段。HKDF 一类 KDF 可以从输入密钥材料派生用途隔离的子密钥:
K_encrypt = HKDF(master, info="atlas/v1/encryption")
K_mac = HKDF(master, info="atlas/v1/webhook-mac")info 用于 domain separation。即使底层主密钥相同,不同协议和用途也不会直接复用同一输出。HKDF 适合高熵密钥材料,不是为了把用户短密码“变安全”。
密码需要故意昂贵的专用函数
用户密码熵低,攻击者拿到数据库后可以离线猜测。通用 hash 太快,反而帮助攻击者。密码验证应使用带独立随机 salt 的 memory-hard password hashing function,例如 Argon2id:
encoded = Argon2id(password, random_salt, memory, iterations, parallelism)Salt 不需要保密,作用是让相同密码产生不同 verifier,并阻止一次预计算覆盖所有账户。每条记录应保存算法、版本、参数、salt 和输出,便于未来升级。
参数不能照抄一个永恒数字。应在生产级硬件和峰值并发下基准测试,把单次验证调到系统可承受、攻击者又足够昂贵的水平,并为登录洪泛预留容量。RFC 9106 给出 Argon2id 参考配置,但真实服务必须结合内存预算和拒绝服务风险调优。
Pepper 是额外的服务器秘密,可存放于 KMS/HSM 并在 hash 前后以受审查方案加入。它能让只泄露数据库的攻击者缺少一部分材料,但会增加轮换和灾难恢复复杂度,不能替代 salt 和合适的 KDF。
登录验证要支持渐进升级
读取记录 → 按记录参数验证
→ 若参数已落后,在本次成功登录后用新参数重算这样无需知道用户明文密码,也能逐步提升成本。长期不登录账户可在风险事件后强制重置。
密码政策同样影响实际安全。NIST SP 800-63B-4 不建议强制大小写、数字、符号的组合规则;应允许长密码和密码管理器粘贴,阻止常见或已泄露密码,限制在线猜测,并在有泄露证据时要求更换,而不是固定周期无故轮换。
单因素密码至少 15 个字符;作为多因素流程的一部分可以允许更短,但不得低于 8 个字符。这是 NIST 的适用场景要求,不代表所有产品都必须机械复制同一最小值;业务仍需依据身份保证等级和监管要求建模。
易混淆关系
| 需求 | 应考虑 | 不应直接使用 |
|---|---|---|
| 文件内容指纹 | SHA-256 等 hash | 带秘密的密码哈希 |
| 共享密钥消息认证 | HMAC | `Hash(key |
| 从主密钥派生用途密钥 | HKDF | 截取 master key 字节 |
| 存储用户密码 | Argon2id 等密码哈希 | SHA-256、可逆加密 |
| 防止 webhook 重放 | MAC + 时间/nonce 状态 | 只有 MAC |
下一课进入非对称密码:公钥解决密钥分发和签名问题,但不会让任意数据都适合“直接用 RSA 加密”。
参考资料
- RFC 2104, HMAC
- RFC 5869, HKDF
- RFC 9106, Argon2 Memory-Hard Function
- NIST, SP 800-63B-4 Authenticator Requirements