怎么生成和保存一个真正安全的密码
怎么生成和保存一个真正安全的密码
"至少 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 | 首选,可调内存、迭代和并行度 |
慢哈希的意义是把单次计算成本抬到几十到几百毫秒——对正常登录无感,对每秒要试几百万次的攻击者是灾难。
用户侧真正有效的三件事
- 每个站点用不同密码。 撞库的逻辑是"一个站泄露,全网账号遭殃",唯一性比复杂度更重要。
- 用密码管理器。 记住 20 个不同的强密码对人来说不现实,让管理器生成并填充,你只需要记住一个主密码。
- 开启两步验证。 优先 TOTP(认证器 App)或硬件密钥(WebAuthn / Passkey);短信验证码最弱,存在 SIM 卡劫持风险。
如果想知道手上这个密码大概有多强,可以用基于 zxcvbn 的估算工具——它不只是数字符,还会识别常见单词、键盘序列(qwerty)、重复模式(aaaa)和日期,估算结果比"含几种字符"这类规则靠谱得多。