怎么生成和保存一个真正安全的密码

"至少 8 位,含大小写、数字和特殊符号"这条规则已经过时了。真正决定密码强度的是信息熵,而熵由长度主导:H = L × log₂N,其中 L 是长度,N 是每一位可用的字符数。

长度为什么比符号重要

看一组对照(组合数量级就是 2^H):

密码形式 N L 熵(位) 组合数量级
8 位小写字母 26 8 37.6 2×10¹¹
8 位大小写+数字+符号 72 8 49.4 7×10¹⁴
12 位小写字母 26 12 56.4 9×10¹⁶
4 词密码短语(7776 词表) 7776 4 51.7 3.7×10¹⁵
6 词密码短语(7776 词表) 7776 6 77.5 2×10²³

关键对比在第二行和第三行:把长度从 8 加到 12(+4 位),熵增加约 19 位;而在 8 位长度内堆满符号,只比纯小写多约 12 位。多一个字符带来的收益,远大于多一类字符。

而 P@ssw0rd 这类"看起来复杂"的密码熵极低——它就在所有破解字典的第一页,符号替换规则(a→@、o→0)早就是字典生成器的默认配置。

熵位数的直观含义

熵每多 1 位,穷举所需的尝试次数翻倍。所以:

  • 40 位左右:在现代 GPU 集群面前不算安全。
  • 50–60 位:能挡住大多数离线破解,前提是服务端用了慢哈希。
  • 70 位以上:即使哈希算法被选得比较差,也仍然很难在可行时间内穷举。

这也是为什么密码短语是好方案:6 个常见词有 77.5 位熵,还比一串随机字符好记。

生成密码短语的正确代码

必须用密码学安全的随机源,不能用 Math.random()。

import secrets

WORDS = ["correct", "horse", "battery", "staple", "purple", "engine"]

def passphrase(n=6):
    return "-".join(secrets.choice(WORDS) for _ in range(n))

print(passphrase())   # 例:battery-engine-staple-correct-purple-engine

上面的示例词表只有 6 个词,实际使用时要用 7776 词(EFF 长词表一类的)才能达到表中标注的熵值——每词的熵是 log₂(词表大小),词表越小熵越低。

浏览器里用 crypto.getRandomValues,并且注意 randomInt 不能简单取模,否则会有取模偏差:

function randomInt(max) {
  const limit = 2 ** 32 - (2 ** 32 % max);   // 拒绝采样,消除取模偏差
  const buf = new Uint32Array(1);
  let x;
  do { crypto.getRandomValues(buf); x = buf[0]; } while (x >= limit);
  return x % max;
}

const passphrase = Array.from({ length: 6 }, () => WORDS[randomInt(WORDS.length)]).join('-');

Python 的 secrets.choice 内部已经处理了偏差,直接用即可。

现代密码策略该怎么写

NIST SP 800-63B 给出的建议和很多老系统相反,这几条值得照着改:

老做法 现在的建议 原因
强制大小写数字符号 不强制组合规则 人只会用固定套路应付,熵反而低
每 90 天强制换密码 不要求定期更换 只会催生 password1 → password2
最短 8 位、上限 16 位 最短 8 位,允许至少 64 位 长密码短语要能存得下
密码提示问题 不要用 "母亲的姓氏"是可以被社工查到的
无 注册和改密时检查是否在已泄露密码库中 撞库远比暴力破解有效

服务端必须怎么存

做法 是否可行
明文存 绝不可行
可逆加密(AES)存 不可行,密钥泄露等于全泄露
MD5 / SHA-1 / SHA-256 不可行,这些是快哈希,每秒可算数百万次
快哈希 + 盐 不可行,盐只防彩虹表,防不住定向穷举
bcrypt / scrypt 可以
Argon2id 首选,可调内存、迭代和并行度

慢哈希的意义是把单次计算成本抬到几十到几百毫秒——对正常登录无感,对每秒要试几百万次的攻击者是灾难。

用户侧真正有效的三件事

  1. 每个站点用不同密码。 撞库的逻辑是"一个站泄露,全网账号遭殃",唯一性比复杂度更重要。
  2. 用密码管理器。 记住 20 个不同的强密码对人来说不现实,让管理器生成并填充,你只需要记住一个主密码。
  3. 开启两步验证。 优先 TOTP(认证器 App)或硬件密钥(WebAuthn / Passkey);短信验证码最弱,存在 SIM 卡劫持风险。

如果想知道手上这个密码大概有多强,可以用基于 zxcvbn 的估算工具——它不只是数字符,还会识别常见单词、键盘序列(qwerty)、重复模式(aaaa)和日期,估算结果比"含几种字符"这类规则靠谱得多。

自己动手试试

相关工具