密码要多少位才够:熵、平均猜测次数和每秒几次

· 6 分钟阅读 · 密码 熵 安全 · English

「这个密码强不强」是可以算的,但算的是它怎么被选出来,不是它长什么样。同一段字符,机器随机抽的和你想出来的,安全性差几个数量级,而屏幕上看起来一模一样。抽样方式、以及攻击者一秒能试几个,才是决定性的两个输入;中间的算术很短,而它看不见复用、钓鱼,也看不见站点当初怎么存这份密码。

一、比特数只来自抽样方式

对随机生成的密码,就一道算术:

比特数 = 长度 × log₂(候选池)
池 每字符比特
26 个小写字母 4.70
62 个大小写加数字 5.95
94 个可见 ASCII 6.55
7776 词表(EFF 骰子口令)每个词 12.93

所以 20 个字符配满 94 池是 131 比特,4 个骰子词只有 51.7 比特。关键在"候选池"三个字:它描述的是抽样器,不是那串字符。Password123! 用满了四类字符、12 个长度,按 94 池算是 78.7 比特——可它不是从 94 个符号里等概率抽的,它是一个单词加了后缀,任何词库加常见变形规则的枚举都会把它排进很前面。同一个公式对你自己敲的密码是天花板,不是测量值。v4 UUID 恰好相反:格式把 128 位里的 122 位定成随机的,所以这个数跟值长什么样无关,UUID 生成器会把它跟每个值一起列出来。

二、猜的是平均值,一半就中

攻击者穷举时平均要试空间的一半,所以计算次数是 2^(比特−1)。这就是为什么表里所有时间都按 (2^bits / 2) ÷ 速度 来给。差的那 1 比特不改变结论,但把一个数字说成"最多"还是"平均",是判断别人算错没有的第一步。

三、真正拉开差距的是"每秒能猜几次"

猜测次数是一回事,速度是另一回事,而且它能差九个数量级:

  • 每秒 100 次:会限流、会锁账号、从不在响应里泄露散列的登录口(一次失败等一秒,锁五分钟就比这更低)。
  • 每秒 10⁴ 次:用 bcrypt / Argon2id 正经配置过的站点被拖库之后——慢哈希是故意做得费工的,这个数还能更小。
  • 每秒 10¹¹ 次:一批 GPU 打一份未加盐的快速散列文件(NTLM,或把 MD5/SHA 当口令散列用的那种)。人们说"几秒就破了"时,心里想的是这一档。

把 (2^bits/2) 除到这三个速度上(表里的值是平均时间,四舍五入):

密码形态 比特 100/s 10⁴/s 10¹¹/s
8 位纯小写 37.6 33 年 121 天 1 秒
12 个十六进制 48.0 4.5 万年 446 年 23 分钟
4 个骰子词 51.7 58 万年 5800 年 5.1 小时
8 位满可见 ASCII 52.4 97 万年 9700 年 8.5 小时
12 位大小写数字 71.5 5115 亿年 51 亿年 512 年
6 个骰子词 77.5 3.5×10¹³ 年 3505 亿年 3.5 万年
12 位满可见 ASCII 78.7 7.5×10¹³ 年 7546 亿年 7.5 万年
20 位满可见 ASCII 131.1 无意义 无意义 4.6×10²⁰ 年

读法:别问"多少位",问"在哪个速度下活多久"。 50 比特在限流登录口是几十万年,在拖了库的快哈希文件上是几分钟——差别不在密码,在那家站点怎么存密码。今天一般给的目标是 70-80 比特以上,因为"站点存得对不对"你不掌握,只能按最坏那一列估。

四、复杂度规则为什么反而更弱

“必须一个大写、一个数字、一个符号"这类规则不增加任何熵:它只是把一部分候选排除掉。熵的减少来自它逼着你把省下的自由度花在可预测的地方——首字母大写、结尾 123!、Spring2024#。规则越细,人越会往模式里挤,而攻击者正是按这个顺序枚举的。

反过来,长度几乎总是更划算:94 池里多一个字符是 +6.55 比特,多两个是 +13.1;而在一个限 12 位的站点上硬塞符号类,等于拿长度换掉一点规则分。真要有意义地变强,只有两条路:更长,或者真的随机。

五、生成器要同时对三件事

  1. 随机数来自操作系统。 crypto.getRandomValues 是浏览器通向同一个熵源的窗口。Math.random 是一个用时钟播种的 xorshift:拿到它输出的几个数就够推出内部状态、猜出后面所有的数——“生成的"变成"可猜的”。浏览器不肯给密码学随机数时,正确行为是拒绝生成,而不是退回一个更安静也更差的东西。
  2. 余数扔掉,不折进去。 值 % 62 不是均匀的:4,294,967,296 除不尽 62,字母表开头那几个字符会摊上余数、出现得略多。正确做法是落进余数区间就重取(拒绝采样)。偏斜很小,但它正是生成器指纹长出来的地方——密码生成器 每一步都走拒绝采样。
  3. 约束之后还要洗牌。 先按要求把"一个大写、一个数字、一个符号"排在开头,会让每个密码长得一样;取完字符再用一次 Fisher–Yates(同样走拒绝采样)打乱位置才对。

顺带一个反直觉点:生成 50 条挑"最好的那条"没有意义——每一条都是同一种抽样得到的,强度相同。挑了第一条,如果某个站点拒绝它,就把被拒的字符填进"永远不要用"再重新生成,别手改,手改正是模式产生的地方。

六、熵算不到的四件事

  • 复用。 一次拖库就把所有账号串起来。熵再高,复用一次就归零——这通常是真人账号失守的头号原因,不是密码太短。
  • 站点侧怎么存。 未加盐的 MD5/SHA、或没配工作因子的 bcrypt,都会把你密码的那一列速度换成上一节表里最坏的一列。这你控制不了,只能选用 Argon2id / bcrypt 的站点。
  • 钓鱼与键盘记录。 密码被"人主动交出去"时,比特数无关。这也是 passkey / 二次验证存在的理由。
  • 剪贴板和屏幕。 复制粘贴是一次真实但很小的暴露(剪贴板与这台机器上所有程序共享),生成器在复制后会提醒这一点。要用的那条放进密码管理器,然后重新生成一次——候选一直显示在屏幕上,而屏幕本身就是那个存储。页面本身什么都不保存:不写本地存储,你打的东西也不会被装进请求发走,输入的内容不会离开这台设备。

相关问题

网站那根强度颜色条能信吗? 不能全信。它只能看你输入的字符串,而同一个字符串是"随机抽的"还是"想出来的”,它无从分辨;很多还会因为你补了 ! 就跳到"很强"。看数字比看颜色好,看抽样方式比看数字好。

多少位算够? 分场景:只在一台限流登录口前(每次几百比特都够用),假设站点被拖库且用慢哈希(60 比特以上开始有意义),假设站点存得很糟(要 80 以上)。密码管理器的主密码建议 6-8 个随机词——它是唯一一个你只记一次、必须按最坏情况估的位置。

勾掉易混淆字符(0O1l|)会不会变弱? 会,但很便宜:那十个字符不再可选,池子从 94 缩到约 84,每字符从 6.55 降到 6.39 比特,12 位少约 2 比特。如果你会在某个别扭的地方手打它,这笔交换值得;由管理器填充就不值得。

为什么"每类至少一个"是可选项而不是默认? 它是给硬性网站留的口子,不是强度特性。一个从完整池随机取的 20 字符密码本来就大概率含齐各类,强行安排等于从一个不缺类的密码上再拿走一点熵。

微信
微信
支付宝
支付宝