为什么”8 位含大小写数字符号”还不够
你大概率见过这类密码建议:
- 至少 8 位,包含大小写 + 数字 + 符号
- 每 90 天更换一次
- 不要使用上次密码的变体
但很少人告诉你:8 位四类字符集的密码,熵约 51.9 bits,现代 GPU 暴力破解只需数小时。
更糟的是,强制更换促使用户选择 Password1! → Password2! 这种最小变体,实际熵几乎没有增加。
本文从熵的本质出发,讲清楚什么是真正的”强密码”,以及如何用现代工具生成符合安全要求的密码。
配套工具:密码生成器
一、熵:密码强度的唯一标尺
1.1 香农熵公式
密码强度的本质是熵(entropy),衡量攻击者需要尝试多少次才能暴力破解。香农熵公式:
熵(bits) = 长度 × log₂(字符集大小)
- 字符集大小:小写 26、大写 26、数字 10、常见符号约 22-32
- 长度:密码字符数
1.2 单字符熵对比
| 字符集 | 字符数 | 单字符熵 |
|---|---|---|
| 仅数字 | 10 | 3.32 bits |
| 仅小写字母 | 26 | 4.70 bits |
| 仅小写 + 数字 | 36 | 5.17 bits |
| 大小写 + 数字 | 62 | 5.95 bits |
| 大小写 + 数字 + 符号 | ~90 | 6.49 bits |
1.3 长度比字符集更重要
这是一个反直觉但关键的结论。对比两个密码:
Aa1!重复 2 次 = 8 位四类字符集:熵 ≈ 51.9 bitscorrecthorsebatterystaple= 25 位仅小写字母:熵 ≈ 117.5 bits
后者熵是前者的 2.3 倍,且更易记忆。这就是为什么 NIST SP 800-63B 不再强制要求复杂字符集,而是鼓励长密码或密码短语(passphrase)。
1.4 长度推荐阈值
| 熵阈值 | 含义 | 推荐长度(四类字符集) |
|---|---|---|
| 28 bits | 在线攻击可破解 | ≥ 5 位 |
| 36 bits | 离线暴力数分钟 | ≥ 6 位 |
| 60 bits | 离线 GPU 数年 | ≥ 10 位 |
| 100 bits | 离线不可行 | ≥ 16 位 |
| 128 bits | 抵御国家级攻击 | ≥ 20 位 |
建议:日常账户密码 ≥ 16 位(熵 ≥ 100 bits),加密密钥 ≥ 20 位(熵 ≥ 128 bits)。
二、CSPRNG vs PRNG:随机数的安全等级
2.1 PRNG(伪随机数生成器)
Math.random 是典型的 PRNG,基于确定性算法(如 xorshift128+)生成”看起来随机”的序列。
状态 = 算法(状态) → 输出
问题:状态可被重建。攻击者收集足够输出(约 2-3 次 64 位输出)即可反推内部状态,预测后续所有”随机数”。这攻击在浏览器 PRNG 上已被实证。
2.2 CSPRNG(加密安全伪随机数生成器)
crypto.getRandomValues 是 CSPRNG,基于操作系统熵池(Linux /dev/urandom、Windows CryptGenRandom)。
特性:
- 即使内部状态泄露,也无法预测后续输出
- 满足”下一比特不可预测性”(next-bit test)
- 满足”状态不可回推”(backtracking resistance)
使用场景:会话令牌、API 密钥、密码、加密密钥、CSRF token、密码重置链接。
2.3 浏览器中的 CSPRNG API
| API | 用途 | 输出 |
|---|---|---|
crypto.getRandomValues(Uint8Array) | 任意长度随机字节 | Uint8Array |
crypto.randomUUID() | UUID v4 | 字符串(推荐用于 UUID 生成) |
crypto.subtle.generateKey() | 密钥对生成 | CryptoKey |
注意:所有 CSPRNG 都需在安全上下文(HTTPS 或 localhost)下使用,部分浏览器在 HTTP 下会限制 crypto.subtle。
三、模偏差与拒绝采样
3.1 什么是模偏差
最朴素的密码生成实现:
const charset = 'abc...XYZ012!@#';
const idx = Math.floor(Math.random() * 256) % charset.length;
return charset[idx];
当 charset.length 不整除 256 时,前面的字符出现概率更高。例如:
- charset.length = 90(四类字符集)
- 256 / 90 = 2 余 76
- 前 76 个字符出现概率 = 3/256 ≈ 1.17%
- 后 14 个字符出现概率 = 2/256 ≈ 0.78%
- 偏差比例:前 76 个字符比后 14 个高约 50%
3.2 拒绝采样(rejection sampling)
正确做法是丢弃落入”非均匀区间”的随机字节:
function pickChar(charset) {
const len = charset.length;
// 计算均匀分布的区间上限
const limit = Math.floor(256 / len) * len;
const buf = new Uint8Array(1);
while (true) {
crypto.getRandomValues(buf);
if (buf[0] < limit) {
return charset[buf[0] % len];
}
// 否则丢弃,重新取
}
}
3.3 重试概率分析
| charset.length | limit | 丢弃区间 | 重试概率 |
|---|---|---|---|
| 10(仅数字) | 250 | 250-255 | 2.3% |
| 26(小写字母) | 234 | 234-255 | 8.6% |
| 62(大小写+数字) | 248 | 248-255 | 3.1% |
| 90(四类+符号) | 180 | 180-255 | 29.4% |
最坏情况约 30% 重试概率,平均重试次数 = 1 / (1 - 0.294) ≈ 1.42 次,性能完全可接受。
3.4 优化:批量取字节
对 90 字符集场景,可一次取 8 字节批量处理,减少 crypto.getRandomValues 调用次数。本工具暂未实现此优化,单次生成 ≤ 128 字符密码性能已足够。
四、字符集策略
4.1 字符集选型矩阵
| 场景 | 推荐字符集 | 长度 | 熵 |
|---|---|---|---|
| 网站账户密码 | 大小写 + 数字 + 符号 | 16 | ~104 bits |
| 密码短语(易记) | 仅小写字母 | 25 | ~117 bits |
| API 令牌 | 大小写 + 数字 | 32 | ~190 bits |
| WiFi 密码 | 大小写 + 数字(排除易混) | 16 | ~94 bits |
| 加密密钥 | 大小写 + 数字 + 符号 | 32 | ~208 bits |
| 一次性口令 | 仅数字 | 6 | ~20 bits |
4.2 排除易混字符
视觉相近字符在常见字体下易混淆:
| 字符 | 易混对象 | 字体场景 |
|---|---|---|
0 | O / o | 无衬线字体 |
1 | l(小写 L)/ I(大写 i) | 等宽字体 |
| ` | ` | l / 1 / I |
' | " | 终端字体 |
` | ' | 终端字体 |
何时勾选:
- 需打印、手抄、口头传达的密码(WiFi、会议口令、应急密码)
- 客服系统中口头告知的临时密码
何时不必勾选:
- 密码管理器自动填充
- 服务端生成、客户端存储的令牌
4.3 符号集设计
本工具符号集为 !@#$%^&*()-_=+[]{};:,.?/,避开以下字符:
| 字符 | 避开原因 |
|---|---|
| 空格 | 粘贴时易截断 |
" ' | SQL/Shell/HTML 注入风险 |
\ | Shell/JSON/正则转义字符 |
< > | HTML 标签边界 |
` | Shell 命令替换 |
这些字符在某些表单中会被拒绝或需特殊转义,避开它们能提升兼容性。
五、强度评估的等级划分
5.1 等级与熵区间
本工具基于熵值(bits)划分 5 个等级:
| 等级 | 熵区间 | 颜色 | 含义 |
|---|---|---|---|
| 极弱 | < 28 | 红 | 秒级破解 |
| 弱 | 28-36 | 橙 | 分钟级破解 |
| 一般 | 36-60 | 黄 | 本地数小时-数天 |
| 强 | 60-100 | 绿 | 在线不可行,离线数年 |
| 很强 | ≥ 100 | 深绿 | 离线也不可行 |
5.2 阈值依据
- 28 bits:在线攻击每秒约 1000 次尝试,28 bits = 2^28 / 1000 / 3600 ≈ 74 小时,分钟到小时级
- 60 bits:现代 GPU 暴力每秒约 10^10 次(如 hashcat + RTX 4090),60 bits = 2^60 / 10^10 / 86400 ≈ 13 天
- 100 bits:分布式 GPU 集群(10^12 次/秒)需 2^100 / 10^12 / 86400 / 365 ≈ 4 × 10^10 年
- 128 bits:理论上不可暴力破解,热力学极限(按 Landauer 原理,遍历 2^128 种状态所需能量超过地球寿命)
5.3 与 zxcvbn 等工具的差异
zxcvbn 等工具评估的是人类记忆密码的实际熵,会识别:
- 字典词(
password实际熵远低于 4.7 × 8 = 37.6 bits) - 日期、姓名、键盘模式
- 替换字符模式(
p@ssw0rd与password实际熵接近)
本工具评估的是理论熵(基于 CSPRNG 生成的最大熵),适用于生成密码而非评估用户输入密码。两者结合使用更佳。
六、记忆密码 vs 密码管理器
6.1 记忆密码(passphrase)
适合主密码(密码管理器主密码、设备解锁密码)。
DiceWare 方法:从 7776 个常见词的词典中随机选 5-7 个词拼接,每词熵约 12.9 bits。
correct horse battery staple yahoo
→ 6 词 × 12.9 = 77.4 bits
优点:
- 易记忆
- 熵足够高
- 可手写输入
缺点:
- 词典泄露后熵下降
- 长度较长(30+ 字符)
6.2 密码管理器
适合日常账户密码。每个账户使用独立随机密码,由密码管理器统一存储。
推荐流程:
- 主密码使用 passphrase(≥ 6 词,熵 ≥ 77 bits)
- 每个账户使用 密码生成器 生成 16 位四类字符集密码(熵 ≈ 104 bits)
- 开启 2FA / MFA 二次验证
- 定期检查 Have I Been Pwned 是否泄露
密码管理器推荐:
- 开源自托管:Bitwarden / Vaultwarden
- 商业服务:1Password、Dashlane
- 本地存储:KeePassXC
6.3 NIST SP 800-63B 现代建议
NIST 在 2017 年发布的 SP 800-63B 附录 A 中明确建议:
- ✅ 最小长度 8 位(用户密码)/ 6 位(PIN)
- ✅ 鼓励长密码(≥ 64 位允许)
- ✅ 允许所有 ASCII 字符(含空格)
- ❌ 不强制复杂字符集(不要求数字+符号+大小写)
- ❌ 不强制定期更换(除非已泄露)
- ✅ 拒绝常见弱密码(top 1000 弱密码字典)
- ✅ 比对泄露数据库(Have I Been Pwned API)
七、工具矩阵联动
本工具与工具盒子其他工具形成「安全」主题矩阵:
| 工具 | 联动场景 |
|---|---|
| 密码生成器 | 生成强密码 |
| UUID 生成器 | 生成会话 ID、API 令牌 |
| Hash 计算 | 密码哈希摘要(避免明文存储) |
| Base64 编解码 | 令牌编码传输 |
| JWT 解码 | 解析含密码的认证令牌 |
| URL 编解码 | 密码重置链接参数 |
7.1 密码生成 + 哈希存储(服务端场景)
# 1. 用密码生成器生成用户初始密码
密码 = "K9#aB3$xY7nQ2mP!" # 16 位四类字符集,熵 ≈ 104 bits
# 2. 服务端用 SHA-256 + salt 哈希存储
echo -n "K9#aB3$xY7nQ2mP!" | sha256sum
# → 3a7d...(用 [Hash 计算](/hash) 工具验证)
# 3. 实际生产用 bcrypt / argon2 而非 SHA-256(防暴力)
7.2 密码生成 + UUID 令牌(API 场景)
# 1. 用户密码(用户登录用)
密码 = "K9#aB3$xY7nQ2mP!"
# 2. API 令牌(程序调用用)
token = "550e8400-e29b-41d4-a716-446655440000" # UUID v4,用 [UUID 生成器](/uuid) 生成
# 3. 令牌 Base64 编码(HTTP 头传输)
echo -n "550e8400-e29b-41d4-a716-446655440000" | base64
# → NTUw...(用 [Base64 编解码](/base64) 工具验证)
7.3 密码重置链接(URL 场景)
# 1. 生成一次性重置令牌
token = "K9#aB3$xY7nQ2mP!" # 16 位四类字符集
# 2. URL 编码(处理特殊字符)
# 用 [URL 编解码](/url) 工具编码
encoded = "K9%23aB3%24xY7nQ2mP%21"
# 3. 拼接重置链接
link = "https://example.com/reset?token=" + encoded
八、扫描兼容性自检清单
生成密码后,按以下清单验证可用性:
- 长度 ≥ 16 位(或熵 ≥ 100 bits)
- 字符集至少包含 3 类(小写 + 大写 + 数字)
- 符号集与目标系统兼容(避开空格、引号、
<>\) - 排除易混字符(需手抄 / 口头传达时)
- 不含个人信息(生日、姓名、电话)
- 不在 Have I Been Pwned 数据库中
- 每个网站使用独立密码
- 已开启 2FA / MFA 二次验证
- 已备份到密码管理器
总结
强密码的本质是熵,不是”看起来复杂”。三个核心要点:
- 长度 > 字符集:16 位仅小写字母的熵(75.2 bits)高于 8 位四类字符集(51.9 bits)
- CSPRNG > PRNG:密码生成必须用
crypto.getRandomValues,不能用Math.random - 拒绝采样消除模偏差:简单
% charsetSize在字符集大小不整除 256 时会引入概率偏差
实践层面:日常密码用 16 位四类字符集(熵 ≈ 104 bits),密码管理器主密码用 6 词 passphrase(熵 ≈ 77 bits),加密密钥用 32 位四类字符集(熵 ≈ 208 bits)。配合 UUID 生成器、Hash 计算、Base64 编解码 等工具,覆盖从生成、存储、传输到验证的完整安全链路。