第4章 密钥派生:基于口令的密钥派生函数(PBKDF2/Argon2)
密钥派生函数,说白了就是把你的口令变成一串固定长度的密钥。我做过不少安全审计,发现很多开发者直接把口令当密钥用——嗯,这是个大坑。你想想看,口令通常只有8~12个字符,而AES-256需要32字节随机密钥,直接拿口令去加密,那攻击者随便猜都能撞出来。
正确的做法是:通过一个专门的函数,把低熵的口令转换成高熵的密钥。这个过程就叫密钥派生(Key Derivation)。本章我们就聊聊最常用的两个家族:PBKDF2和Argon2。
4.1 为什么不能直接用口令?
先看一个简单的对比:
| 属性 | 口令 | AES密钥 |
|---|---|---|
| 长度 | 通常 6~20 字节 | 固定 16/32 字节 |
| 熵 | 低(约 2~4 bit/字符) | 高(128~256 bit) |
| 格式 | 可打印ASCII | 二进制随机 |
| 抗暴力破解 | 弱(每秒可猜数万次) | 强(枚举空间天文数字) |
所以密钥派生函数要干三件事:
- 拉伸:把短口令变成长密钥。
- 加盐:每个用户用不同的随机盐,防止预计算攻击。
- 慢速:让暴力破解变得极慢,增加攻击成本。
4.2 PBKDF2:经典但正在退场
PBKDF2(Password-Based Key Derivation Function 2)是PKCS#5标准的一部分。它的核心思想是重复迭代HMAC。你可能觉得简单,但它在移动设备上性能还行。
参数看下表:
| 参数 | 说明 | 典型值 |
|---|---|---|
| PRF | 伪随机函数,常用HMAC-SHA256 | HMAC-SHA256 |
| Salt | 随机盐,防彩虹表 | 16 字节 |
| c | 迭代次数 | 100000~600000 |
| dkLen | 派生密钥长度(字节) | 32(AES-256) |
选迭代次数是个学问。我记得2018年有个项目,用户登录响应慢得不行,一查迭代次数设了1000万——那是在树莓派上跑的。后来我建议降到50万,并且用HMAC-SHA256,效果好了不少。
代码实现很简单(Python标准库支持):
import hashlib, binascii
dk = hashlib.pbkdf2_hmac(
'sha256',
b'my_password',
b'salt_1234567890abcdef',
600000,
dklen=32
)
print(binascii.hexlify(dk))
4.3 Argon2:现代密码学的答案
Argon2是2015年密码学竞赛的胜出者,专门设计来抵抗GPU和ASIC。它有三种变体:
- Argon2d:抗侧信道攻击弱,但速度稍快,适合加密货币。
- Argon2i:抗侧信道强,适合密钥派生。
- Argon2id:混合模式,既抗侧信道又抗暴力破解。推荐通用方案。
我用Argon2id最多。它的参数要这样配:
| 参数 | 作用 | 推荐值 |
|---|---|---|
| time_cost | 迭代次数 | 2~3 |
| memory_cost | 内存消耗(KiB) | 65536(64 MiB) |
| parallelism | 并行线程数 | 1~4 |
| salt_len | 盐长度(字节) | 16 |
| tag_len | 输出密钥长度 | 32 |
你可能好奇为什么memory_cost要设这么高。举个例子,Argon2在计算时会持续占用64 MiB内存,这意味着攻击者想并行跑,显存/内存很容易被填满。我曾经测试过,同样的计算量,Argon2比PBKDF2慢10倍以上——这正是我们想要的。
Python用起来也很直观:
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3,
memory_cost=65536,
parallelism=4,
hash_len=32,
salt_len=16
)
hash_str = ph.hash("my_password")
# 验证
ph.verify(hash_str, "my_password")
4.4 一张图理解全部流程
下面这张SVG图展示了两条路径:PBKDF2和Argon2各自如何从口令到密钥。我故意把两个流程并列,这样你一眼就能看出差异。
4.5 我踩过的坑和总结
讲两个真实教训。
第一个坑:迭代次数设太低。 我曾经接手过一个旧系统,PBKDF2迭代次数只有1000。攻击者用普通笔记本,每秒能算20万次。我改到60万后,登录时间从0.1秒变成了3秒——但安全提升了600倍,值。
第二个坑:盐重用。 有个项目为了省事,所有用户用一个全局盐。结果一个用户字典泄露,所有用户的派生密钥都能预计算。嗯,这相当于没加盐。以后记住:每个用户的盐必须唯一且随机。
最后说一句:如果你在2025年还在用PBKDF2做新项目,建议赶紧升级到Argon2id。如果历史遗留系统没法换,把迭代次数调到60万以上,至少能挡一挡。