密码安全的真正战场
讨论密码强度之前,先明确攻击模型——因为不同的攻击场景,决定完全不同的防御重点:
| 攻击场景 | 攻击者能做什么 | 有效防御 |
|---|---|---|
| 撞库(credential stuffing) | 拿其他站泄露的账号密码批量试 | 每站独立密码 + 泄露库筛查 |
| 在线爆破 | 直接对着你的登录接口试 | 限流 + 渐进延迟 + 锁定策略 |
| 离线爆破 | 已拿到数据库哈希,本地慢慢算 | 慢哈希(Argon2id / bcrypt)+ 强盐 |
| 钓鱼 | 诱骗你在假网站输入 | 通行密钥 / WebAuthn |
| SIM 劫持 | 接管你的手机号 | 不用短信做第二因素 |
大多数人以为自己在防「离线爆破」,实际被攻破的原因是撞库——也就是同一个密码在多个站复用。这也是为什么「每站独立随机密码」比「一个超强但通用的密码」重要得多。
判断一个密码到底有多强,可以先用 密码强度检测 量一下,再看下面的原理。
密码熵:公式与它的局限
理想公式
E = log₂(R^L),R 是字符集大小,L 是密码长度:
| 字符集 | 大小 | 8 位 | 12 位 | 16 位 |
|---|---|---|---|---|
| 纯数字 | 10 | 26.6 bit | 39.9 bit | 53.2 bit |
| 小写字母 | 26 | 37.6 bit | 56.4 bit | 75.2 bit |
| 大小写 + 数字 | 62 | 47.6 bit | 71.4 bit | 95.3 bit |
| 全部可打印字符 | 95 | 52.6 bit | 78.9 bit | 105.2 bit |
推荐基线:随机生成的密码 ≥ 12 位、熵值 > 70 bit。
公式失效的地方
这个公式假设每个字符独立均匀随机。人不是随机源,所以实际熵远低于名义值:
| 密码 | 名义熵(95 字符集) | 实际可破解性 |
|---|---|---|
P@ssw0rd1 |
~52 bit | 字典 + 常见替换,几万次内命中 |
Qwerty123! |
~52 bit | 键盘序列 + 数字后缀,极快 |
Zhang1985! |
~52 bit | 常见姓氏 + 出生年,定向字典秒破 |
Tr0ub4dor&3 |
~65 bit | 仍是「单词 + 替换 + 后缀」结构 |
结论:长度比字符种类重要得多。 12 位纯随机小写字母(约 56 bit,实际也接近 56 bit)远强于 8 位四种类混合(名义 52.6 bit,实际可能不到 30 bit)。
zxcvbn:更接近现实的估算
现代强度检测(zxcvbn 及其衍生实现)不数字符集,而是先识别模式再估算猜测次数:
- 字典词(含常见人名、地名、品牌、影视角色)
- 常见替换(
a→@、o→0、i→1) - 键盘序列(
qwerty、1qaz2wsx) - 重复与序列(
aaaa、12345) - 日期(
1985、20240101) - 结构拆分(
word+123+!分开算,再乘组合数)
这才是「为什么 correct-horse-battery-staple 强」的正确解释:四个随机常见词,组合空间是词表大小的四次方,而人脑只需记四个词。
破解速度:为什么哈希算法选型是生死线
同一份密码哈希,在不同算法下的离线爆破速度差几个数量级:
| 哈希算法 | 单张现代 GPU 每秒可试 | 8 位全字符集爆破耗时 |
|---|---|---|
| MD5 | ~10¹¹ 次 | 秒级 |
| SHA-256 | ~10¹⁰ 次 | 秒级 |
| bcrypt(cost 12) | ~10³ 次 | 数年 |
| Argon2id(推荐参数) | ~10² 次 | 数十年 |
关键结论:MD5/SHA 系列是快速哈希,设计目标是校验完整性,绝不能用来存密码——它们是给「追求速度」的场景用的,而密码存储恰恰需要「追求慢」。
存储侧的选型与参数细节见 密码哈希指南:bcrypt、argon2 与 scrypt 对比,需要实测哈希输出时可用 bcrypt 哈希工具;算法输出长度与安全性对照见 哈希算法速查表。
NIST 已经推翻的三条老规矩
很多系统的密码策略还停留在 2003 年的 NIST 附录 A,而 SP 800-63B 已经明确反转:
| 老规矩 | 现状 | 原因 |
|---|---|---|
| 每 90 天强制改密码 | ❌ 不推荐 | 用户做可预测小改动,安全性反降 |
| 强制大小写 + 数字 + 符号 | ❌ 不推荐 | 把用户推向 P@ssw0rd1 这类模式 |
| 密码提示问题 | ❌ 应移除 | 答案多为公开信息 |
| 最小长度 ≥ 8 | ✅ 保留(建议 ≥ 12) | 长度是唯一稳定有效的复杂度来源 |
| 筛查已泄露密码 | ✅ 强烈推荐 | 直接消灭撞库的主要弹药 |
| 允许粘贴密码 | ✅ 推荐 | 否则会阻碍密码管理器 |
泄露密码筛查:k-匿名性怎么做到的
「检查用户密码是否在泄露库里」听起来要拿明文去比对,实际不需要——HIBP 的 Range API 用 k-匿名性 解决:
- 客户端计算密码的 SHA-1 哈希(注意:仅用于查询,不用于本地存储)
- 只把哈希的前 5 个字符发给服务端
- 服务端返回所有前缀匹配的哈希后缀(通常几百条)
- 客户端在本地比对完整哈希是否命中
服务端从头到尾没见过你的密码,也没见过完整哈希。目前该库收录量已达数亿级别。
MFA 与通行密钥:选型取舍
| 因素类型 | 抗钓鱼 | 抗 SIM 劫持 | 用户体验 | 建议 |
|---|---|---|---|---|
| 通行密钥 / WebAuthn | ✅ 强 | ✅ | 最好(生物识别一键) | 首选 |
| 硬件安全密钥 | ✅ 强 | ✅ | 好(需携带) | 高权限账号 |
| TOTP 应用 | ❌ 可被实时中转 | ✅ | 中(需输 6 位) | 通用次选 |
| 短信 SMS | ❌ | ❌ | 好 | 仅兜底 |
| 邮箱验证码 | ❌ | ✅ | 中 | 取决于邮箱本身强度 |
通行密钥为什么天然抗钓鱼:凭据的私钥绑定在特定域名下,浏览器/系统只允许对匹配的域名签名。假网站即使在 UI 上伪装成真网站,也拿不到可用于真网站的签名——因为域名不匹配。这一点 TOTP 做不到:用户在假网站输入了 6 位码,攻击者可以立刻拿去真网站用。
服务端防护清单
密码安全不只是「让用户设强密码」,服务端这一侧的漏洞更常见:
存储
- 永远不存明文,也不用可逆加密(密钥泄露等于全泄露)
- 用 Argon2id(首选)或 bcrypt(cost ≥ 12)+ 每用户独立随机盐(≥ 16 字节)
- 不要用 MD5 / SHA-1 / SHA-256 裸哈希,也不要自己拼接 salt 后套 SHA
- 保留算法标识,便于未来平滑升级(用户下次登录时重哈希)
登录接口
- 限流按账号而非 IP(按 IP 会让 NAT 后的正常用户互相误伤)
- 失败次数递增延迟,
5 次后锁定 15 分钟比「永久锁定」好——后者本身就是拒绝服务漏洞 - 登录失败提示不要区分「用户不存在」和「密码错误」(会泄露账号是否注册)
- 重置令牌要一次性、短时效(≤ 30 分钟)、用后即焚,可用 令牌生成器 生成高熵值令牌
传输与日志
- 全站 HTTPS,登录页 HTTP 直接 301
- 日志中绝不记录密码、完整令牌、重置链接
- 不要在 URL query 里传令牌(会进浏览器历史、Referer、代理日志)
监控
- 检测异常登录(新设备、异地、短时间大量失败)
- 接入泄露密码筛查,命中即强制改密
个人实践清单
- 每个重要站点用独立随机密码(靠密码管理器,不靠记忆)
- 主密码用 4–6 个随机词组成的口令,并开启 MFA
- 邮箱和主密码管理器优先开通行密钥——这两个失守,其他账号都能被重置
- 有通行密钥选项就开,别再选短信
- 定期用泄露检测查一遍自己的常用账号
- 不要因为「这个站不重要」就复用密码——撞库正是利用这一点