2. PBKDF2算法:算法原理、HMAC迭代过程、盐值的重要性、迭代次数的选择
PBKDF2,全称 Password-Based Key Derivation Function 2。说白了,它就是一把「密码磨豆机」——你把人类容易记的密码扔进去,它反复研磨,最终吐出一把能直接给加密算法用的密钥。
我最早接触 PBKDF2 是在做移动端本地数据加密的时候。当时想当然地直接用 SHA-256 哈希密码,结果被安全审计打了回来。嗯,从那以后我才真正搞明白,密码不能直接当密钥用。
2.1 算法核心流程
PBKDF2 的数学表达式看起来有点唬人:
DK = PBKDF2(PRF, Password, Salt, c, dkLen)
拆开来看,其实就五个参数:
- PRF:伪随机函数,通常就是 HMAC-SHA256
- Password:用户输入的密码
- Salt:盐值,随机生成的辅助数据
- c:迭代次数,决定磨豆子的遍数
- dkLen:期望输出的密钥长度
整个流程可以概括为:加盐 → 反复 HMAC → 拼接输出。我习惯把它想象成一个三层循环:外层是迭代次数,中层是每个输出块,内层是 HMAC 的异或累加。
核心要点:PBKDF2 的安全性不依赖算法本身的复杂度,而是依赖「计算成本」——让攻击者每猜一次密码都要付出巨大的计算代价。
2.2 HMAC 迭代过程详解
PBKDF2 的每一轮迭代,本质上就是反复调用 HMAC。我举个例子你就明白了:
// 第一轮
U1 = HMAC(Password, Salt || INT(1))
// 第二轮
U2 = HMAC(Password, U1)
// 第三轮
U3 = HMAC(Password, U2)
// ... 一直迭代 c 次
// 最终输出块
T1 = U1 ⊕ U2 ⊕ U3 ⊕ ... ⊕ Uc
这里有个细节:异或累加。每一轮的 HMAC 输出不是直接扔掉,而是和之前的结果做异或。这样做的好处是——即使某一轮的 HMAC 输出被破解,攻击者也无法还原出完整的密钥。
我的经验:曾经有个项目把异或写成了拼接,结果密钥长度翻倍,性能暴跌。调试了一下午才发现是这里错了。写代码时一定要看清楚标准文档里的 ⊕ 符号。
为什么会设计成异或而不是拼接?你想想看,如果直接拼接,输出长度会随着迭代次数线性增长。而异或能保证输出长度始终等于 HMAC 的输出长度(比如 SHA-256 就是 32 字节),这样上层调用者不用关心迭代次数,接口更干净。
2.3 盐值的重要性
盐值,说白了就是给每个用户分配一个「随机调料包」。没有盐值的 PBKDF2,和直接哈希密码没什么区别。
我见过最典型的翻车案例:某系统直接用用户名当盐值。攻击者拿到数据库后,发现用户名为 "admin" 的哈希值,直接去彩虹表查 "admin" 的预计算值——一秒破解。
警告:盐值必须满足三个条件:
- 随机生成:每个用户独立,不能用用户名、邮箱等可预测数据
- 长度足够:至少 16 字节,推荐 32 字节
- 唯一性:同一系统内不能出现重复盐值
盐值的作用可以总结为一张表:
| 场景 | 无盐值 | 有盐值 |
|---|---|---|
| 彩虹表攻击 | 一次预计算,破解所有用户 | 每个用户需要单独预计算 |
| 相同密码 | 哈希值相同,一眼看出 | 哈希值不同,无法关联 |
| 并行破解 | 可批量 GPU 加速 | 每个盐值独立计算,无法并行 |
| 存储成本 | 只需存哈希值 | 多存 16-32 字节盐值 |
我个人习惯把盐值和哈希值一起存储,格式类似 salt:hash。这样验证时直接截取前 32 字节作为盐值,不用额外查表。
2.4 迭代次数的选择
迭代次数是 PBKDF2 唯一可调的安全参数。选少了不安全,选多了用户骂娘。怎么平衡?
我记得 2017 年做金融项目时,安全规范要求迭代次数不低于 10 万次。当时用的手机是骁龙 835,10 万次 HMAC-SHA256 大约耗时 80ms。用户登录时感觉不到延迟,这个值就定下来了。
但到了 2023 年,同样的 10 万次在服务器上只需要 5ms。攻击者的 GPU 集群呢?可能只需要 0.1ms。所以迭代次数必须定期上调。
选型建议:
- 2024 年推荐值:至少 60 万次 HMAC-SHA256
- 性能预算:控制在 100ms-500ms 之间(用户可接受范围)
- 动态调整:系统应支持「渐进式升级」——用户登录时用新迭代次数重新哈希
这里有个坑:迭代次数不是越大越好。我曾经见过一个项目设了 500 万次,结果用户登录要等 3 秒,投诉电话被打爆。后来降到 100 万次,配合服务器端缓存,才勉强能用。
选迭代次数时,我建议你按这个步骤来:
- 确定目标硬件(用户手机?服务器?嵌入式设备?)
- 写一个基准测试脚本,测量 1 万次 HMAC 耗时
- 用「目标耗时 ÷ 单次耗时 × 1万」算出初始迭代次数
- 留 20% 余量,应对未来硬件性能下降(比如用户用老手机)
避坑指南:我曾经在 IoT 设备上用过 PBKDF2,迭代次数设了 1000 次。结果发现设备 CPU 不带硬件加速,一次登录要 2 秒。后来换成 100 次,配合硬件安全模块才解决。嵌入式场景一定要实测,别信理论值。
最后说一句:PBKDF2 虽然经典,但它有个硬伤——抗 GPU 攻击能力弱。因为 HMAC 的计算逻辑简单,GPU 可以大规模并行。如果你面对的是高安全场景(比如加密货币钱包),建议考虑 scrypt 或 Argon2,它们对内存带宽有要求,GPU 加速效果大打折扣。
嗯,PBKDF2 的核心内容就这些。记住三个关键词:加盐、迭代、异或累加。把这三点搞明白,你就能在项目中正确使用它了。