手动想一个复杂密码这件事,从第一步就输了:人不会随机,只会改装。随机密码生成在线服务真正值钱的不是那个「生成」按钮,而是它背后的随机源和你选的长度。
下面是熵的公式、一张能直接对照的破解时间表,以及几条已经被证伪的老规矩。
一、手动想密码为什么会失败
人脑没有随机性。 让人随口报十个「随机」数字,结果会集中在 1 和 2 开头、避开重复与规律。攻击者的字典就是按人的偏好建的。
改装规则是固定的。 首字母大写 + 结尾补 1 或 !,是全人类默认的改装方式。hashcat 的规则文件(best64.rule、OneRuleToRuleThemAll)里写的就是这些变换。
跨站复用。 一个站被拖库,你在其它站的账号同时失守——这是当下最常见的攻击方式,第七节细说。
二、密码强度由什么决定:长度 > 字符空间
强度只有一个公式:
H = log2(N^L) = L × log2(N)
H:信息熵,单位 bit,衡量「攻击者平均要试多少次」
N:字符空间大小(每个位置有多少种可能)
L:长度
关键在于两者位置不对等:L 是乘数,N 要先被取对数。字符空间翻一倍,每字符只多 1 bit;长度翻一倍,总熵直接翻倍。
| 字符集 | N | 每字符贡献的熵 |
|---|---|---|
| 纯数字 | 10 | 3.32 bit |
| 纯小写 | 26 | 4.70 bit |
| 小写 + 大写 | 52 | 5.70 bit |
| 大小写 + 数字 | 62 | 5.95 bit |
| 再 + 常见符号(共 88 个) | 88 | 6.46 bit |
从纯小写堆到四类混合,每字符只从 4.70 涨到 6.46,涨幅不到 40%;而长度从 8 加到 16,什么都不用做,熵已经翻倍。
三、长度 vs 破解时间对照表
表的口径先说清楚:口令库泄露 + 攻击者知道你的生成规则 + 服务端存的是未加盐的快哈希,取离线猜测速度 1e10 次/秒,平均命中需要 2^(H-1) 次尝试。
| 密码形态 | 熵 | 平均尝试次数 | 破解耗时(1e10 次/秒) |
|---|---|---|---|
| 8 位纯数字 | 26.6 bit | 5×10^7 | 瞬间 |
| 8 位小写 | 37.6 bit | 1×10^11 | 约 10 秒 |
| 8 位四类混合 | 51.7 bit | 1.8×10^15 | 约 2 天 |
| 10 位小写 | 47.0 bit | 7×10^13 | 约 2 小时 |
| 12 位小写 | 56.4 bit | 4.8×10^16 | 约 55 天 |
| 12 位四类混合 | 77.5 bit | 1.1×10^23 | 约 34 万年 |
| 16 位四类混合 | 103.4 bit | 6.5×10^30 | 约 20 万亿年 |
换成能感知的结论:8 位四类混合在 GPU 面前只有两天寿命——而这正是多数网站注册校验的最低标准。
以上假设服务端用的是 MD5 / SHA 这类快哈希。换成 bcrypt / Argon2,破解成本会抬高几个数量级,但那是服务端的选择,你唯一能做的就是给自己加位数。
四、12 位小写打败 8 位四类,但网站拦的偏偏是前者
const entropy = (L, N) => L * Math.log2(N)
entropy(12, 26) // 56.4 bit —— 12 位纯小写
entropy(8, 88) // 51.7 bit —— 8 位含大小写数字符号
12 位纯小写比 8 位四类混合强 26 倍(2^4.7)。
讽刺的是:abcdefghijklmn 会被绝大多数注册表单拒绝(「必须包含大小写字母和数字」),而 Ab3!xy7z 一路放行——后者弱了 26 倍。
这套「必须凑齐三类」的要求源自 2003 年 NIST SP 800-63 附录的一段建议,起草人后来公开表示它已被证明是错的:它恰好鼓励了「首字母大写 + 结尾补符号」这种最易预测的变形。
所以实操只有一句:能选长就优先拉长。本站长度滑杆是 4–64,建议 16 起步;站点限制 12 位以内时,再把四类全勾上补熵。
把「数量」调到 1,结果区会显示这条密码的字符空间、信息熵 bit 数和按 1e10 次/秒 估算的破解耗时——上面表格里的数字可以逐个验证。
五、随机密码生成在线背后的随机源:为什么 Math.random() 不行
这是最容易跳过、也最要命的一节。
// 不推荐:随机源不安全,且存在模偏差
function weakPick(pool, len) {
let s = ''
for (let i = 0; i < len; i++) s += pool[Math.floor(Math.random() * pool.length)]
return s
}
// 推荐:CSPRNG + 拒绝采样
function randInt(max) {
const arr = new Uint32Array(1)
const limit = Math.floor(0xffffffff / max) * max // 去掉尾部的偏斜区间
let x
do {
crypto.getRandomValues(arr)
x = arr[0]
} while (x >= limit)
return x % max
}
两个坑要分开说:
坑一:Math.random() 不是加密安全的随机数。 它是伪随机数发生器(V8 里是 xorshift128+ 一类算法),输出之间存在可推导的数学关系。已有公开研究指出,拿到足够多的连续输出可以反推内部状态,进而预测后续取值。用它生成密码、token、验证码,等于把秘密值拱手让人。
坑二:就算换成 CSPRNG,% pool.length 仍有模偏差。 2^32 通常不是字符池大小的整数倍,靠前的字符更容易被选中。上面的 do...while 是标准的拒绝采样修正,本站的生成器正是这么实现的。
Node 一侧对应 crypto.randomBytes() 和 crypto.randomInt()。
六、容易被拖库字典命中的弱密码长什么样
它们的问题不是「短」,而是可被预测生成:
| 特征 | 典型样本 |
|---|---|
| 季节 + 年份 + 感叹号 | Spring2026!、Summer@2026 |
| 键盘走位 | qwerty、1qaz2wsx、qazwsx |
| 公司 / 产品名 + 数字 | Company123、Admin@123 |
| 拼音 + 生日 | zhangsan1998 |
| 序列 / 重复 | 12345678、aaaa1111 |
Spring2026! 12 位且四类字符齐全,按熵公式算看着不低——但熵公式只对真正随机生成的密码成立。攻击方拿着公开泄露字典加变形规则,几秒就能覆盖到这种模式。
记住:熵衡量的是随机性,不是复杂度。 人想出来的密码,熵永远接近 0。
七、撞库、彩虹表、加盐分别在说什么
- 拖库:站点数据库被拿走,账号密码对流入黑市,是所有后续攻击的起点
- 撞库(credential stuffing):拿 A 站泄露的账号密码批量登录 B 站。它不破解任何东西,只利用你的复用行为
- 彩虹表:预先算好的「明文 → 哈希」对照表,本质是空间换时间
- 加盐(salt):给每个用户一段随机值,存
hash(salt + password),让通用彩虹表作废,攻击者必须逐人重算
要补一句防误解:加盐只挡批量攻击,挡不住针对单人的定向爆破。 真正让离线爆破变贵的是慢哈希:
// Node 里存密码的正确姿势(示意)
import bcrypt from 'bcryptjs'
const stored = await bcrypt.hash(password, 12) // cost 每 +1,算力成本翻倍
await bcrypt.compare(input, stored) // 永远比对,绝不解密
用 bcrypt / scrypt / Argon2 / PBKDF2 加随机盐存储,选哪个不重要,不要用裸 MD5 / SHA-256 直接存。
想直观看看「同明文必然同摘要」这个问题(也就是加盐要解决的),可以拿哈希工具算同一个字符串,再改一个字符看结果如何雪崩:
八、「定期强制改密码」这条规矩已经被推翻
NIST SP 800-63B 从 2017 年那一版起就把「周期性更换密码」从建议里删除了,明确要求验证方不得在没有泄露证据的情况下强制用户改密码。
理由很朴素:强制到期会逼出 P@ssw0rd1 → P@ssw0rd2 → P@ssw0rd3 这种可递推序列,也会让人把密码写在便签上。攻破一次等于攻破全部。
NIST 现在推荐的做法是:
- 长度优先,最短 8 位,且至少允许 64 位(现实中不少站点反而卡 16 位上限)
- 不强制字符种类组合
- 注册与改密码时比对公开泄露库,命中就拒绝
- 废弃「你母亲的名字」这类知识型安全问题
- 提供两步验证,并允许用户粘贴(密码管理器要能用)
所以入职培训里那句「90 天必须改一次」是过期策略。真正有效的组合只有一句:每个站点一个随机长密码 + 开启两步验证。
九、网站存明文就是流氓,怎么自查
判断方法很简单:点「找回密码」,看它发的是一次性重置链接,还是把你的原密码原样显示在邮件里。能读出来就是明文或可逆加密存储,立刻改密码并停用该站。
其它信号:注册表单把长度卡在 6–16 位、禁止粘贴、禁止空格,基本可以推断是老旧系统加弱存储。
密码是否已经进了泄露库,唯一靠谱的自查渠道是 Have I Been Pwned 的口令查询。它用 k-匿名模型:本地算 SHA-1,只上传前 5 位,服务端返回这一批后缀后在本机比对,完整密码与完整哈希都不出本机。
要说透一点:没有任何在线工具能查看别人数据库里的明文密码,声称能查的一律是诈骗。
十、生成工具是配角,密码管理器才是正解
生成器的价值有限:它只解决「这一瞬间给我一个足够随机的串」,但人记不住 30 个随机串。让系统真正跑起来的是密码管理器——它同时解决记忆、每站唯一、自动填充和泄露提醒。
分工是这样:
- 主密码:唯一需要记住的一个,用随机词短语而不是符号串。骰子词表每词约 12.9 bit(
log2(7776)),4 个词 51.7 bit,5 个词 64.6 bit,好记又好输入 - 其余站点:交给管理器生成,长度给到对方允许的上限
- 在线生成:用于临时场景——测试账号、一次性口令、共享口令
把生成结果复制进记事本是不算数的,那只是换了个地方明文躺着。
十一、密码卫生清单
- 主密码改成 4–5 个随机词组成的短语,不搞符号堆砌
- 装一个密码管理器,每个站点一个随机串,长度给到该站允许的上限(不低于 16)
- 在 HIBP 上把已有邮箱全查一遍,命中的站点立刻改
- 找回密码能读出原密码的站点,标记为不可信,换专用密码或停用
- 邮箱、支付、网盘这三类账号必须开两步验证(优先 TOTP 或硬件密钥,短信次之)
- 复用密码的站点优先整改,尤其是同一密码改后缀用在多处的
- 不再设「90 天改一次」的闹钟,改成「收到泄露通知才改」
- 开发侧:密码用 bcrypt / Argon2 加随机盐存储;随机源一律用
crypto.randomInt(),任何时候都不要碰Math.random()
相关工具:随机密码生成器(可调长度与字符集)|哈希摘要计算|UUID 生成
全部纯前端实现,输入的内容不上传服务器。