宝塔拨测博客拨测技术博客
宝塔拨测

密码生成器生成的密码够强吗?长度、字符集与熵的换算

密码生成器给出一个 32 位密码,界面标着「非常强」。这个标签不是感觉,是算出来的:熵 = 长度 × log2(字符集大小)。搞懂这个公式,你就知道该调长度还是调字符集,也知道「非常强」在什么情况下会骗人。

打开 在线密码生成器,默认是 32 位,勾了大写、小写、数字三类,符号没勾:

默认 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 位纯数字是「强」,两者都跟「有没有符号」没关系。

16 位、四类全勾,强度显示强,密码以 o+JA 开头

这带来一个实际判断:看到「非常强」先别急着用,回头确认两件事——多少位、勾了哪几类。一条 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 标点,不含空格、中文标点和全角符号。部分站点不接受其中的某些符号(比如引号、反斜杠),遇到这种限制就取消勾选符号,改用更长的字母数字组合。

密码生成器密码强度熵随机密码密码安全宝塔拨测