工具指南8-在线密码生成器

你现在有多少个账号?邮箱、社交媒体、银行、云服务、开发者平台、各种 SaaS 工具……大多数人有 50-100 个线上账号,重度用户可能超过 200 个。

再问一个问题:你有多少个不同的密码?

如果答案远小于账号数量,你并不孤单。根据 NordPass 的统计,全球最常用的密码仍然是 123456,排名第二的是 password。即使是技术从业者,也经常在”安全”和”方便记忆”之间妥协——用一个基础密码加点变体,在不同网站间复用。

问题是,这个妥协的代价可能很大。这篇文章从密码破解的角度讲清楚:什么样的密码才算强?“随机”到底意味着什么?以及怎么高效地生成和管理密码。

密码是怎么被破解的

在讨论”好密码”之前,先看看攻击者是怎么破解密码的。了解攻击方式,才能针对性地防御。

暴力破解(Brute Force)

最直接的方式:把所有可能的字符组合全试一遍。

假设密码是 8 位纯小写字母,那一共有 26^8 ≈ 2088 亿种组合。听起来很多,但现代 GPU 每秒可以计算数十亿次哈希。用一张 RTX 4090 跑 MD5 哈希,大约每秒 1640 亿次(来源:Hashcat 基准测试)。也就是说,8 位纯小写字母的密码,不到 2 秒就能穷举完毕

字典攻击(Dictionary Attack)

不逐字符穷举,而是用常见密码列表去匹配。网上公开的密码字典动辄上亿条,都来自历次数据泄露事件。如果你的密码是 qwerty123iloveyou,基本在字典的前 1000 条里。

规则变异攻击(Rule-based Attack)

字典攻击的升级版。攻击者在字典词基础上应用规则:首字母大写、末尾加数字、把 a 替换成 @、把 o 替换成 0。所以 P@ssw0rd 看起来”复杂”,但对于规则攻击来说和 password 没什么区别。

彩虹表攻击(Rainbow Table)

预计算大量密码的哈希值,存成查找表。拿到哈希后直接查表,省去实时计算的开销。这就是为什么现代系统都要对密码做加盐(salt)处理——相同密码加不同盐,哈希结果完全不同,彩虹表就失效了。

撞库攻击(Credential Stuffing)

用 A 网站泄露的用户名和密码,去 B、C、D 网站尝试登录。如果你在多个网站用同一个密码,一次泄露就意味着全部沦陷。

密码强度的核心指标:熵

密码学中衡量密码强度的标准指标是信息熵(Entropy),单位是比特(bit)。

计算公式:

熵 = log2(字符集大小 ^ 密码长度)
   = 密码长度 × log2(字符集大小)

字符集大小取决于你使用的字符范围:

字符类型字符集大小每个字符的熵
纯数字(0-9)103.32 bit
纯小写字母(a-z)264.70 bit
大小写字母(a-z, A-Z)525.70 bit
大小写 + 数字625.95 bit
大小写 + 数字 + 特殊字符956.57 bit

一些具体的例子:

  • 123456(6位纯数字):6 × 3.32 = 19.9 bit — 极弱
  • password(8位纯小写):8 × 4.70 = 37.6 bit — 弱
  • Tr0ub4dor&3(XKCD #936 的例子,11位混合):理论上限约 72 bit,但因基于字典词 “troubador” 的 leet speak 变体,实际熵远低于此(XKCD 估算约 28 bit)
  • K#9xMp!2vL$w(12位全字符集):12 × 6.57 = 78.8 bit — 强
  • correct horse battery staple(4个随机单词,基于约 2048 词表):约 44 bit — 看似简单但比字典变体密码更强

业界普遍建议的密码强度:

  • 最低可接受:50 bit 以上
  • 推荐:70 bit 以上
  • 高安全场景(主密码、加密密钥):80 bit 以上

一个反直觉的事实

注意上面的例子:correct horse battery staple 是 4 个普通英文单词组成的口令,看起来很简单,但实际熵并不低。这来自 XKCD 的经典漫画 #936——它的核心观点是:长度比复杂度更重要

但这里有个前提:这 4 个词必须是随机选取的。如果是你自己想出来的”有意义”的短语,比如 i love my dog,那熵会大幅下降,因为攻击者会针对常见短语建字典。

为什么”自己想”的密码不够随机

人类天生不擅长产生随机性。心理学实验反复证明,让人”随机”选数字,结果总是偏向某些模式:

  • 倾向于避免重复(真正的随机序列经常有重复)
  • 偏好某些数字(7 被选中的概率远高于理论值)
  • 受近因效应影响(刚看到什么就倾向选什么)

密码也一样。让人”想一个随机密码”,大多数人会:

  • 用单词或名字做基础
  • 用可预测的替换规则(@ 代替 a1 代替 l
  • 在末尾加数字或符号

这些模式都在攻击者的规则库里。所以真正安全的密码必须由算法生成,不能靠人脑。

密码生成的技术实现

一个合格的密码生成器需要满足两个条件:密码学安全的随机数生成器(CSPRNG)足够大的字符空间

浏览器端的安全随机数

在浏览器环境下,Math.random() 是不安全的——它使用的是伪随机数算法(通常是 xorshift128+),可预测、可复现。正确的做法是使用 Web Crypto API:

// 生成安全的随机字节
const array = new Uint32Array(1);
crypto.getRandomValues(array);

// 生成指定长度的密码
function generatePassword(length, charset) {
  const result = [];
  const charsetArray = [...charset];
  for (let i = 0; i < length; i++) {
    const randomValues = new Uint32Array(1);
    crypto.getRandomValues(randomValues);
    const index = randomValues[0] % charsetArray.length;
    result.push(charsetArray[index]);
  }
  return result.join("");
}

const password = generatePassword(16, 
  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
);

crypto.getRandomValues() 底层调用操作系统的 CSPRNG(Linux 的 /dev/urandom、Windows 的 BCryptGenRandom),安全性有保障。

取模偏差(Modulo Bias)

上面的代码有一个细微问题:randomValues[0] % charsetArray.length 存在取模偏差。

Uint32 的范围是 0 到 4294967295。如果字符集大小不能整除 4294967296,那某些字符被选中的概率会略高于其他字符。比如字符集大小为 95,4294967296 / 95 = 45210182 余 6,意味着前 6 个字符的概率比后 89 个略高。

在实际应用中这个偏差小到可以忽略,但如果你追求完美,可以用拒绝采样(rejection sampling):

function secureRandomIndex(max) {
  const limit = Math.floor(0x100000000 / max) * max;
  let value;
  do {
    const buf = new Uint32Array(1);
    crypto.getRandomValues(buf);
    value = buf[0];
  } while (value >= limit);
  return value % max;
}

这样每个字符的选中概率就完全相等了。

Node.js 环境

在 Node.js 中,对应的 API 是 crypto 模块:

const crypto = require("crypto");

function generatePassword(length, charset) {
  const result = [];
  for (let i = 0; i < length; i++) {
    const randomByte = crypto.randomInt(charset.length);
    result.push(charset[randomByte]);
  }
  return result.join("");
}

crypto.randomInt() 内部已经处理了取模偏差问题,直接用就好。

Python 实现

Python 3.6+ 的 secrets 模块专门为密码学场景设计:

import secrets
import string

def generate_password(length=16):
    charset = string.ascii_letters + string.digits + string.punctuation
    return "".join(secrets.choice(charset) for _ in range(length))

print(generate_password())

注意不要用 random 模块——它的 Mersenne Twister 算法不是密码学安全的。

密码策略的选择

了解了原理,回到实际问题:日常使用中,密码应该怎么设置?

普通账号

  • 长度:14-16 位
  • 字符集:大小写字母 + 数字 + 特殊字符
  • 熵:约 85-105 bit
  • 生成方式:密码管理器或在线密码生成器自动生成
  • 不需要记忆,靠密码管理器存储

主密码(密码管理器、系统登录)

  • 推荐使用 Passphrase(随机词组)
  • 4-6 个随机选取的常见英文单词
  • 熵:约 44-77 bit(EFF Diceware 7776 词表:4 词约 51.7 bit,6 词约 77.5 bit;常见 2048 词表:4 词约 44 bit)
  • 优点:方便记忆,输入不易出错
  • 例如:marble sunset anchor diagram velocity

不同场景的最低要求

场景最低长度推荐熵说明
论坛、测试账号12 位50 bit低价值账号
邮箱、社交媒体14 位70 bit账号恢复枢纽
银行、支付16 位80 bit高价值目标
主密码、加密密钥20 位或 5 词90+ bit最高安全等级

密码管理的现代方案

生成强密码只是第一步,管理才是长期挑战。

密码管理器

目前最推荐的方案是使用密码管理器(如 1Password、Bitwarden、KeePass)。核心逻辑:

  1. 记住一个强主密码
  2. 其他所有密码由管理器生成和存储
  3. 每个网站用不同的随机密码
  4. 自动填充,不需要手动输入

这样即使某个网站发生数据泄露,影响范围也仅限于那一个账号。

双因素认证(2FA)

强密码 + 2FA 是当前最佳实践。即使密码泄露,没有第二因素也无法登录。优先使用 TOTP(如 Google Authenticator)或硬件密钥(如 YubiKey),尽量避免 SMS 验证码(存在 SIM 卡劫持风险)。

定期更换的误区

传统安全策略要求每 90 天更换密码。但 NIST 在 SP 800-63B 中明确反对这种做法——频繁更换导致用户选择更弱的密码(因为要记住),反而降低了整体安全性。正确的策略是:使用强随机密码,只在确认泄露时才更换

在线工具的使用场景

虽然密码管理器是最佳方案,但有些场景下在线密码生成器更方便:

  • 临时需要一个强密码,手边没有密码管理器
  • 为新环境配置初始密码(服务器、数据库、API Key)
  • 需要批量生成密码(测试数据、批量开通账号)
  • 想快速评估不同参数对密码强度的影响

密码生成器支持自定义密码长度、字符集组合(大小写字母、数字、特殊字符),并实时显示密码强度评分。所有计算在浏览器本地完成,密码不会发送到服务器。

一个实用技巧:如果需要在命令行环境快速生成密码,可以用一行命令代替:

# macOS / Linux
openssl rand -base64 24 | tr -d '/+=' | head -c 16

# 或者用 /dev/urandom
LC_ALL=C tr -dc 'A-Za-z0-9!@#$%^&*' < /dev/urandom | head -c 16

但如果是给普通用户用,图形化的在线工具显然门槛更低。

几个常见误解

“密码越长就越安全” — 长度确实是最重要的因素,但前提是随机的。aaaaaaaaaaaaaaaa 有 16 位,但熵几乎为零。

“特殊字符让密码更安全” — 加入特殊字符增大了字符集,但效果远不如增加长度。12 位纯字母密码(约 68 bit)比 8 位全字符集密码(约 53 bit)更强。

“密码强度检测器说’强’就安全了” — 大多数网站的强度检测只看字符种类和长度,不检查实际的随机性。Password1! 通常会被判为”强”,但实际在字典攻击面前不堪一击。

“记住的密码比随机密码安全” — 恰恰相反。人能记住的密码,攻击者也能猜到。密码安全的本质是对抗计算能力,不是对抗人的记忆。

总结

密码安全归结为三个原则:

  1. 随机性:使用 CSPRNG 生成,不要自己编造
  2. 长度优先:14 位以上,能长则长
  3. 唯一性:每个账号一个独立密码

在实践中,用密码管理器管理所有账号密码,用一个强 Passphrase 作为主密码,再加上 2FA,就能覆盖绝大多数安全需求。需要临时生成密码时,在线密码生成器是个顺手的工具。

安全不是一次性的事,而是持续的习惯。


本系列其他文章