密码生成器生成的密码够强吗?长度、字符集与熵的换算
密码生成器给出一个 32 位密码,界面标着「非常强」。这个标签不是感觉,是算出来的:熵 = 长度 × log2(字符集大小)。搞懂这个公式,你就知道该调长度还是调字符集,也知道「非常强」在什么情况下会骗人。
打开 在线密码生成器,默认是 32 位,勾了大写、小写、数字三类,符号没勾:

三类字符共 26 + 26 + 10 = 62 个,32 位就是 32 × log2(62) ≈ 190.5 比特。这个数才是「非常强」背后的东西。
熵怎么算:长度 × 每个字符有多少种可能
工具的四类字符集实测大小是:大写 26、小写 26、数字 10、符号 32(从 ! 到 ~ 之间除字母数字外的 ASCII 标点,不含空格),四类全选共 94 个。
| 字符集 | 大小 | 8 位 | 12 位 | 16 位 | 20 位 | 32 位 |
|---|---|---|---|---|---|---|
| 只有数字 | 10 | 26.6 | 39.9 | 53.2 | 66.4 | 106.3 |
| 只有小写字母 | 26 | 37.6 | 56.4 | 75.2 | 94.0 | 150.4 |
| 大写 + 小写 + 数字 | 62 | 47.6 | 71.5 | 95.3 | 119.1 | 190.5 |
| 四类全选 | 94 | 52.4 | 78.7 | 104.9 | 131.1 | 209.7 |
(单位:比特,按 长度 × log2(字符集大小) 估算。)
这张表里有两个反直觉的地方:
- 32 位纯数字(106.3 比特)和 16 位四类字符(104.9 比特)几乎一样强。 也就是说,一条「必须含大小写数字符号」的 16 位密码,实际强度和 32 位纯数字差不多,并没有因为符号多就安全一截。
- 32 位纯小写字母(150.4 比特)反而高于 20 位四类字符(131.1 比特)。 长度带来的收益,比字符集带来的大得多。
所以「加长」通常优先于「加符号」。字符集从 62 扩到 94,每多一位只多 0.6 比特;而长度每多 8 位,在 62 字符集下就多 47.6 比特。
「非常强」是按熵分档,不是按有没有符号
在同一页面把长度和字符集逐个改过去,界面的强度标签是这样变化的:
| 设置 | 估算熵(比特) | 强度标签 |
|---|---|---|
| 1 位,四类全选 | 6.6 | 弱 |
| 8 位,四类全选 | 52.4 | 中等 |
| 12 位,四类全选 | 78.7 | 中等 |
| 16 位,四类全选 | 104.9 | 强 |
| 20 位,四类全选 | 131.1 | 非常强 |
| 32 位,四类全选 | 209.7 | 非常强 |
| 32 位,只有数字 | 106.3 | 强 |
| 32 位,只有小写字母 | 150.4 | 非常强 |
| 32 位,只有大写字母 | 150.4 | 非常强 |
规律很清楚:标签只跟估算熵走,跟字符种类无关。32 位纯小写字母是「非常强」,32 位纯数字是「强」,两者都跟「有没有符号」没关系。

这带来一个实际判断:看到「非常强」先别急着用,回头确认两件事——多少位、勾了哪几类。一条 32 位纯小写密码熵有 150.4 比特,界面会给「非常强」,但它过不了「必须含数字」的注册规则。
勾了四类,生成结果不保证每类都有
这是实测里最容易踩的一条。四类全勾之后,连续生成并逐条检查是否同时包含大写、小写、数字、符号:
| 长度 | 生成次数 | 四类齐全 | 占比 |
|---|---|---|---|
| 4 位 | 150 | 10 | 6.7% |
| 8 位 | 200 | 86 | 43.0% |
| 12 位 | 200 | 153 | 76.5% |
| 16 位 | 200 | 167 | 83.5% |
8 位里超过一半的密码至少缺一类;即使 16 位,也有 33 次不合格。原因不是工具坏了,而是它从 94 个字符里逐位等概率抽取:数字只占 10 个,某一位抽不到数字的概率是 84/94,16 位全部抽不到数字的概率约 16.5%,再算上缺大写、小写或符号的小概率,至少缺一类的概率约 17–18%,与实测 33/200 基本一致。
![8 位四类全勾,强度只有中等,密码为 9G!.]c2k](/blog/images/pwgen-8-all.png)
所以遇到「必须包含大写字母、数字和符号」这类强制规则,正确做法是生成后逐类数一遍,不合格就点「重新生成」。不要手工在末尾补一个数字——补上去的那一位就固定了,等于自己削掉一点随机性;工具重抽一次是免费的。
顺带一提,16 位连续生成 200 次,200 个结果互不相同,没有出现重复。随机性本身没问题,问题只在「类别覆盖」这件事上不保证。
随机性来自浏览器,密码不经过服务器
工具页面写明它用的是 crypto.getRandomValues,所有生成操作在浏览器本地完成,密码不会上传到服务器。
这一点值得单独说,因为 Math.random() 和它不是一回事:后者是伪随机数生成器,只要知道内部状态就能预测后续输出,不能用来生成密码;crypto.getRandomValues 取的是操作系统提供的密码学随机源,浏览器规范里就是为这类用途准备的。
本地生成的另一面是:页面能生成密码,也就能读取你在它上面输入的任何内容。所以「生成新密码」可以用在线工具,「检查一个已有密码」不要往陌生网页的输入框里粘——那和把密码直接发出去区别不大。
长度拉到 512 之前,先看对面收不收
工具的长度上限是 512 位,滑杆上标了 1、128、256、384、512。但密码不是越长越有用,接收方会先给你设限:
- 有的系统对密码长度有上限,超过会被截断或直接拒绝;
- 用 bcrypt 存密码时只处理前 72 字节,512 位 ASCII 密码后面的 440 字节根本不参与计算,熵再多也用不上。72 字节四类字符约 471.9 比特,已经远超实际需要。
常规账号用 16–24 位随机密码,配合密码管理器一站一密,比把长度拉到 512 更实际。需要手输、不能复制的场景(比如开机密码),与其背 32 位随机串,不如用几个词组成的长口令——长度够、还能记住。
最后一个边界情况:四类字符全部取消勾选时,工具不会生成空密码,而是把「重新生成」按钮置灰,并在长度下方提示「至少需要勾选一个字符集」,强度显示「无效」。

常见问题
密码生成器默认生成的 32 位密码有多强?
默认勾选大写、小写、数字三类(符号默认未勾),字符集共 62 个字符,32 位约 190.5 比特熵,界面显示「非常强」。如果连符号一起勾上,字符集变成 94 个,同样的 32 位约 209.7 比特。默认值已经够用,真正的短板通常是你把它重复用在多个站点。
想加强密码,加长度还是加字符集?
加长度更划算。字符集从 62 扩到 94,每多一位只多约 0.6 比特(log2(94) 减 log2(62));而长度每多 8 位,在 62 字符集下就多约 47.6 比特,是前者的几十倍。把 12 位加到 20 位,比把三类字符扩成四类的收益大得多。
为什么生成的密码里没有数字?不是勾了数字吗?
勾选只代表数字进入候选池,不保证每一位都出现。工具是从合并后的字符集里逐位等概率抽取,数字只占 94 个字符中的 10 个。实测 16 位密码 200 次里有 167 次四类齐全,其余 33 次至少缺一类;8 位时只有 86 次齐全。遇到站点强制「必须含数字」,生成后数一遍,不合格就点重新生成。
生成的密码会被上传到服务器吗?
工具页面写明使用浏览器原生 crypto.getRandomValues 生成,所有生成操作在浏览器本地完成,密码不会上传到服务器。这和 Math.random 不是一回事:后者是伪随机,种子可预测;crypto.getRandomValues 取的是系统密码学随机源。
长度拉到 512 是不是最安全?
熵确实更高,但要看接收方。有的系统限制密码长度,超出会被截断或拒绝;用 bcrypt 存密码时只处理前 72 字节,512 位 ASCII 密码后面的 440 字节根本不参与计算。常规账号 16–24 位随机密码已经足够,把上限拉满更多是浪费。
生成的密码符号里为什么没有我想要的?
工具的符号集是 32 个 ASCII 标点,不含空格、中文标点和全角符号。部分站点不接受其中的某些符号(比如引号、反斜杠),遇到这种限制就取消勾选符号,改用更长的字母数字组合。