引言:为什么密码依然重要
自 1960 年代分时计算机系统诞生的早期阶段起,密码一直是用户与其数据之间的主要守门人。1961 年,麻省理工学院(MIT)的研究员 Fernando Corbató 率先在兼容分时系统(CTSS)中引入了密码——这最初并非作为一种安全措施,而仅仅是为了给每个用户提供私有的文件空间。六十多年后,密码仍然是互联网上最广泛使用的身份验证机制,保护着从电子邮件账户到银行系统的所有内容。
然而,普通用户通常在几十个网站上重复使用密码,选择像 Summer2024! 这样可预测的模式,并且对什么是真正难以猜测的密码缺乏直觉。本文将对密码安全进行深入的技术探讨:如何计算熵,为什么随机性至关重要,最新的指南说了什么,以及如何养成能够真正保护您的习惯。
理解熵:不可预测性的数学
在信息论中,熵 (Entropy) 衡量不可预测性。对于密码而言,它回答了一个问题:攻击者平均需要猜测多少次才能破解该密码?
计算公式为:
H = L × log₂(N)
其中:
- H = 以位(bits)为单位的熵
- L = 密码长度(字符数)
- N = 字符集的大小(可能字符的范围)
字符集大小
| 字符集 | 大小 (N) | 每字符位数 |
|---|---|---|
| 仅小写字母 | 26 | 4.7 bits |
| 小写 + 大写 | 52 | 5.7 bits |
| 字母数字 | 62 | 5.95 bits |
| 完整可打印 ASCII | 94 | 6.55 bits |
| 扩展 ASCII / Unicode | 128+ | 7+ bits |
熵示例
| 密码 | L | N | 熵 (H) |
|---|---|---|---|
password |
8 | 26 | 37.6 bits |
P@ssw0rd |
8 | 94 | 52.4 bits |
k9$mQzLw |
8 | 94 | 52.4 bits |
xK#7pL!qR2@v |
12 | 94 | 78.6 bits |
correct horse battery staple |
28 | 26 | 131.9 bits |
拥有 128 位以上熵的密码被认为在当前技术条件下是计算上无法通过暴力破解的。作为参考,256 位 AES 加密(被认为是不可破解的)对应于 2²⁵⁶ 种组合的密钥空间。
CSPRNG 与 Math.random():为什么随机源至关重要
并非所有的随机数都是平等的。安全密码生成器与不安全生成器之间的区别通常在于 随机源。
Math.random() —— 不适用于安全领域
JavaScript 内置的 Math.random() 是一个 伪随机数生成器 (PRNG)。它速度快且统计上均匀,但它 不是加密安全的。它的内部状态可以从其输出中推断出来,这意味着观察到足够多数值的攻击者可以预测未来的数值。
// ❌ 不安全 —— 请勿用于密码生成
function insecurePassword(length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}
crypto.getRandomValues() —— 正确的工具
Web Cryptography API 提供了 crypto.getRandomValues(),它从操作系统的 加密安全伪随机数生成器 (CSPRNG) 中提取熵。在 Linux 上是 /dev/urandom;在 Windows 上是 CryptGenRandom。这些源从硬件事件(按键、磁盘 I/O、网络时序)中收集熵,被认为是加密强度高的。
// ✅ 安全 —— 使用 CSPRNG
function generatePassword(length, charset) {
const array = new Uint32Array(length);
crypto.getRandomValues(array);
return Array.from(array, (val) => charset[val % charset.length]).join('');
}
const charset =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?';
console.log(generatePassword(16, charset));
// 示例输出: "aK7!xQz2#Lp9@Wm5"
关于取模偏差的注意: 当
charset.length不能整除 2³² 时,上面的示例会存在轻微的取模偏差。对于生产代码,应使用拒绝采样来完全消除这种偏差。
字符集与复杂性
大多数密码生成器提供以下开关:
- 小写字母 (a–z):26 个字符
- 大写字母 (A–Z):26 个字符
- 数字 (0–9):10 个字符
- 符号 (!@#$%^&*…):约 32 个字符
结合这四种类型可以得到一个拥有 94 个可打印 ASCII 字符 的池。密码中每增加一个字符,搜索空间就会乘以 94。一个由 94 个字符组成的 12 位密码拥有 94¹² ≈ 4.76 × 10²³ 种组合——即使对于专门的硬件来说,这也是一个天文数字。
为什么长度胜过复杂性
许多遗留系统要求“至少一个大写字母、一个数字、一个符号”,但允许 8 位密码。一个由 94 个字符组成的 8 位密码仅有 94⁸ ≈ 6 × 10¹⁵ 种组合——使用现代 GPU 集群在几小时内即可破解。相比之下,将密码延长至 16 位纯小写字母(26¹⁶ ≈ 4.4 × 10²²)实际上比 8 位复杂密码提供了 更多 的熵。
NIST SP 800-63B:现代密码指南
美国国家标准与技术研究院的 NIST 特别出版物 800-63B(数字身份指南,2017 年发布,2024 年更新)推翻了许多广为流传的密码假设:
NIST 现在的建议
- 用户选择的密码最少 8 个字符;机器生成的密码最少 15 个字符。
- 长度重于复杂性 —— 强制性的复杂性规则(大写+数字+符号)并不能显著提高安全性,反而会让用户感到沮丧。
- 根据泄露列表检查密码 —— 拒绝在已知数据泄露中发现的密码(例如,通过 HaveIBeenPwned API)。
- 除非有妥协证据,否则不要定期强制轮换 —— 强制轮换会导致可预测的模式,如
Summer2024→Autumn2024。 - 无组合规则 —— 不要要求特定的字符类型;相反,允许所有可打印字符,包括空格。
- 限制速率并锁定 身份验证尝试,以防止在线攻击。
这些指南反映了一个现实:在复杂性限制下,人类行为是可预测的:人们为了满足符号要求而附加 !,仅将首字母大写,并使用易于记忆的年份。
密码管理器:实用的解决方案
大多数人可以做出的最大安全改进是采用 密码管理器。密码管理器可以:
- 为每个账户生成强大且唯一的密码
- 将它们存储在加密库中(通常是 AES-256)
- 在浏览器和应用程序中自动填充凭据
- 提醒您重复使用或已泄露的密码
流行的密码管理器
| 工具 | 类型 | 显著特点 |
|---|---|---|
| Bitwarden | 开源,云端/自托管 | 免费层级,经过审计 |
| 1Password | 商业,云端 | 旅行模式,家庭计划 |
| KeePass | 开源,本地 | 完全离线,插件生态系统 |
| KeePassXC | 开源,本地 | 跨平台 KeePass 分支 |
| Dashlane | 商业,云端 | 暗网监控 |
密码管理器的主密码应该是长且易于记忆的密码短语——这是您唯一需要记住的密码。
密码短语与 Diceware 方法
对于您需要 记住 的密码(例如密码管理器的主密码),密码短语 (Passphrases) 远优于复杂的短字符串。
Diceware 如何工作
Diceware 方法由 Arnold Reinhold 于 1995 年创建,使用物理骰子来生成真正的随机单词选择:
- 获取 EFF 大型单词列表(7,776 个单词,索引为 11111–66666 的 6 进制数)
- 掷 5 个骰子 得到一个 5 位数字(例如,2-4-1-3-6 → 24136)
- 查找对应的单词(例如,“clump”)
- 重复 6–8 次以构建密码短语
从 7,776 个单词中提取的 6 个单词的 Diceware 密码短语拥有:
H = 6 × log₂(7776) = 6 × 12.93 ≈ 77.6 bits
“correct horse battery staple”(由 XKCD #936 普及)包含来自约 2,000 个常用词汇表的 4 个单词,提供约 44 位的熵——虽然具有启发性,但太短了。实际建议使用来自完整 Diceware 列表的六个或更多单词。
常见的密码攻击
了解攻击有助于您调整防御措施。
字典攻击
攻击者使用单词列表——范围从数百万到数十亿个条目——结合 基于规则的变异(首字母大写、附加数字、用 @ 代替 a)。像 Hashcat 这样的工具每秒可以应用数千个变异规则。任何可以通过简单替换从字典单词衍生出来的密码都是脆弱的。
暴力破解攻击
纯暴力破解尝试每一种组合。使用专用的 GPU 设备(8× RTX 4090),常见哈希算法的破解速度为:
| 哈希算法 | 速度 (H/s) | 破解 8 位(94 字符集)的时间 |
|---|---|---|
| MD5 | 约 200 GH/s | 约 8 小时 |
| SHA-1 | 约 70 GH/s | 约 24 小时 |
| bcrypt (成本 10) | 约 184 kH/s | 约 1,100 年 |
| Argon2id | 约 1 kH/s | 约 200,000 年 |
此表说明了为什么在服务器端进行 正确的密码哈希 至关重要——但也说明了为什么 16 位随机密码即使对于 MD5 哈希也具有抵抗力。
彩虹表
预先计算的哈希到密码的表格,用存储空间换取速度。通过 加盐 (Salting) 可以完全击败彩虹表——在哈希之前为每个密码添加一个唯一的随机值。像 bcrypt 和 Argon2 这样的现代算法在设计上就包含了加盐。
凭据填充 (Credential Stuffing)
使用从一次泄露中获得的用户名/密码对来攻击其他服务。防御措施是 每个网站使用唯一的密码——这正是密码管理器的闪光点。
为什么基于浏览器的密码生成更安全
我们的工具完全在您的浏览器中使用 JavaScript 生成密码。数据永远不会离开您的设备。这之所以重要,原因如下:
- 无服务器传输 —— 密码永远不会接触网络数据包。
- 无服务器日志 —— 没有什么可以被传唤、泄露或破坏。
- 生成时无第三方依赖 —— 生成过程中没有 API 调用,没有外部脚本。
- 可复现 —— 您可以检查源代码以验证逻辑。
将其与服务器端生成器进行比较:即使使用 HTTPS,生成的密码也存在于服务器内存中,可能出现在访问日志中,并且完全依赖于运营商的可信度。
相关的 Web API 非常简单:
// 浏览器的 CSPRNG —— 在所有现代浏览器中可用
const buffer = new Uint8Array(32);
self.crypto.getRandomValues(buffer);
// buffer 现在包含 32 个加密随机字节
密码存储:bcrypt、scrypt 和 Argon2
当服务存储密码时,它们 绝不应该存储明文 或可逆的加密形式。正确的方法是使用 密码哈希函数 (PHF) —— 一种专门为此目的设计的缓慢、单向函数。
bcrypt
由 Niels Provos 和 David Mazières 于 1999 年设计,bcrypt 包含一个 成本因子 (work factor),可以随着硬件变快而增加。成本为 12 意味着 Blowfish 密钥设置的 2¹² = 4,096 次迭代。今天是标准做法,得到广泛支持。
scrypt
由 Colin Percival 于 2009 年设计。内存密集型 —— 除了 CPU 时间外,还需要大量的 RAM,使得 GPU/ASIC 攻击成本高昂。参数:N(CPU/内存成本)、r(块大小)、p(并行化)。
Argon2
2015 年密码哈希竞赛 (PHC) 的获胜者。三种变体:
- Argon2d:抗 GPU,易受侧信道攻击
- Argon2i:抗侧信道攻击,抗 GPU 能力较弱
- Argon2id:混合型 —— 推荐的默认值
Argon2id 配合 m=65536(64 MB 内存)、t=3(3 次迭代)、p=4(4 个线程)是目前新应用程序的黄金标准。
双因素身份验证:必不可少的补充
即使是完美的密码也可能通过网络钓鱼、键盘记录器或数据泄露被盗。双因素身份验证 (2FA) 确保仅凭被盗的密码是不够的。
2FA 方法(从最弱到最强)
| 方法 | 机制 | 攻击防御能力 |
|---|---|---|
| 短信验证码 | 通过短信发送代码 | 易受网络钓鱼、SIM 卡克隆攻击 |
| TOTP (Google Authenticator) | 基于时间的 6 位代码 (RFC 6238) | 易受实时网络钓鱼攻击 |
| 推送通知 | 在手机上批准/拒绝 | 易受网络钓鱼(MFA 疲劳攻击) |
| 硬件密钥 (FIDO2/WebAuthn) | YubiKey, Passkey | 抗网络钓鱼 |
| Passkeys (通行密钥) | 设备绑定的加密密钥 | 最强;取代密码 |
FIDO2/WebAuthn 硬件密钥和 Passkeys 在设计上就具有抗网络钓鱼能力,因为加密挑战-响应绑定到了确切的域名。虚假网站无法重放凭据。
最佳实践总结
- 使用密码管理器 —— 为每个账户生成并存储唯一的密码。
- 敏感账户最少 16 个字符;其他地方最少 12 个字符。
- 启用 2FA —— 首选 FIDO2/WebAuthn 或 TOTP;尽可能避免使用短信。
- 绝不重复使用密码 —— 一个网站的泄露不应危及其他网站。
- 检查 HaveIBeenPwned —— 验证您的电子邮件地址和密码未出现在已知泄露中。
- 为您的主密码使用密码短语 —— 6 个以上 Diceware 单词,易于记忆,强度极高。
- 警惕“复杂性剧场” ——
P@ssw0rd123远比xK8mLq2vZnRj脆弱。 - 优先考虑长度 —— 20 个随机小写字母 (94 位) 优于 12 个复杂字符 (78 位)。
- 立即更新被泄露的密码 —— 仅在受到威胁时轮换,而不是按随意的计划轮换。
- 使用泄露检查工具 —— Firefox Monitor 或 1Password Watchtower 等服务会持续监控您的账户。
常见问题解答
问:我的密码应该多长? 对于大多数账户,来自完整 94 字符集的 16 个随机字符提供约 105 位的熵 —— 绰绰有余。对于高价值账户(银行、电子邮件、密码管理器),请使用 20 个以上字符或 6 个单词的密码短语。
问:使用在线密码生成器安全吗? 只有当所有生成都发生在客户端(在您的浏览器中)且没有服务器通信时才安全。我们的工具满足这一要求。通过检查浏览器的网络选项卡进行验证 —— 生成密码时不应触发任何请求。
问:我应该包含符号吗? 符号增加了每个字符的熵(6.55 位对比仅小写字母的 5.17 位),所以如果网站允许,请包含符号。但是,某些网站会限制某些符号。一个不含符号的长密码可以达到含有符号的短密码的熵。
问:量子计算机能破解我的密码吗? Grover 算法为量子计算机在暴力破解攻击中提供了二次加速,实际上将安全位减半。一个 256 位密钥变成了约 128 位安全。对于密码,这意味着需要 256 位熵的密码才能实现长期的抗量子性 —— 这可以通过来自 94 个字符集的 40 个字符的密码(262 位)实现。对于大多数当前威胁,128 位熵(20 个随机字符)已经绰绰有余。
问:像 ILovePizza2024 这样好记的密码有什么问题?
它们容易受到字典攻击。Hashcat 的规则引擎可以轻松生成数百万个常用短语的变体,包括 leetspeak、大小写切换和附加数字。即使是看似个人的密码也遵循攻击者可以进行统计建模的模式。
问:此工具如何生成随机性?
我们使用 crypto.getRandomValues() —— 由您的操作系统的 CSPRNG 提供支持的 Web Cryptography API。这与您的浏览器中用于 TLS/SSL、SSH 密钥生成和其他加密操作的随机源相同。