密码生成器背后的算法:crypto.getRandomValues如何保证真随机

不谈代码,用大白话讲清楚为什么Math.random生成的密码是“假随机”,以及浏览器内置的加密级随机数有多可靠

你可能经常用到在线密码生成器,点一下按钮就出来一串乱码,看起来足够乱。但你有没有想过,这些密码到底是怎么“随机”出来的?

如果随机算法不够好,生成的密码就会有规律可循,再长也是白搭。所以这篇文章想聊一个平时不太被关注、但直接决定密码是否真正安全的底层问题——随机数生成算法

一、普通随机数 vs. 密码学安全随机数

几乎所有编程语言都自带一个简单的随机数函数,JavaScript里叫 Math.random()。它用起来很方便,随便一调用就给你一个0到1之间的小数。但它有一个致命缺陷:它的随机是“看起来随机”,而不是“真正无法预测”

Math.random() 属于一种叫“伪随机数生成器(PRNG)”的算法,常见的是 xorshift128+。这种算法有一个特点:只要你知道了它的内部状态,后面的所有随机数都能被推算出来。

打个比方,就像一台自动售货机,你只要知道它这一刻卡在第几颗糖果,就能知道下一颗掉出来的是什么口味的。而普通PRNG的内部状态是可以被逆向出来的——前提是攻击者拿到了足够多的连续输出值。

也就是说,如果用 Math.random() 来生成密码,虽然表面上看起来够乱,但如果攻击者知道你生成了很多个密码,理论上可能推算出你的生成规律。

而密码学安全伪随机数生成器(CSPRNG)不一样。它有两个硬指标:

  • 下一比特不可预测(next-bit test):给你前面所有输出,你也猜不出下一个比特是0还是1
  • 抗状态恢复:即使攻击者拿到了大量输出,也没办法倒推出内部种子

这就是我们在线密码生成器使用的 crypto.getRandomValues() 和普通 Math.random() 的本质区别。

二、crypto.getRandomValues 到底从哪里拿到随机数

我们经常在浏览器里调用的 crypto.getRandomValues(),本质上是向操作系统索要真正的熵源。现代操作系统(Windows、macOS、Linux、iOS、Android)都维护着一个“熵池”,这个池子的随机性来自哪里呢?

  • 鼠标移动的精确时间间隔
  • 键盘敲击的微秒级时间差
  • 硬盘读写的延迟抖动
  • 网络包的到达时间
  • 甚至还有CPU内置的硬件随机数发生器(如Intel RDRAND指令)

这些信号掺杂在一起,经过哈希运算后,就能产生高质量的随机数。我们每次点“生成密码”,背后就是从这个混合了物理世界信息的熵池里抓一把随机字节,再映射成你看到的字母、数字和符号。

这也解释了为什么这个页面明确告诉你“所有密码在本地生成”——因为生成过程根本不需要联网,全靠你设备自己的操作系统提供真随机数。

三、我们做了哪些额外的安全处理

即使有了 crypto.getRandomValues,直接拿原始随机字节拼凑密码仍然有一些细节问题。我们的生成器做了几层额外保护:

1. 拒绝采样消除模偏差

假如你有94种可能的字符,直接把随机字节对94取余来选,会让前面几个字符出现的概率略微高于后面的。我们用了“拒绝采样”——简单说就是随机值如果落在一个不公平的区间就重新取,直到每个字符的概率完全均等。这可能会让生成慢几微秒,但对安全性来说值得。

2. Fisher-Yates洗牌保证位置随机

为了保证“至少每种字符类型各一个”的前提下,这些字符在密码中出现在哪个位置也是完全随机的,我们用了经典的Fisher-Yates洗牌算法。洗牌所用的随机索引同样来自 crypto.getRandomValues,确保每一步都是加密级随机。

3. 本地生成,不传服务器

这条你可能听我们反复讲,但放在这里更有说服力:整个过程从熵源取数、字符映射、到最终展示,全部在你自己的浏览器里完成。没有任何一步需要把密码或中间结果发送到任何服务器。这意味着即使我们的服务器被攻破,也不会影响你的密码安全性。

四、和在线API生成方式的对比

有些工具通过调用远程API来生成密码,比如向某个服务器请求一个随机字符串。这在安全模型上完全不同:

  • 你的密码经过了一次网络传输,可能被中间人截获
  • 服务端可能会记录日志,甚至明文存储你请求的密码
  • 服务商的随机算法你不能直接审查,只能“相信”

我们的在线密码生成器因为完全在本地运行,你可以通过浏览器开发者工具直接查看源码,验证我们有没有偷偷上传数据。有兴趣的话,在页面上按Ctrl+U就能看到完整的JavaScript代码。

一个小实验:

如果你想直观感受一下Math.random和crypto.getRandomValues的区别,可以打开浏览器控制台,连续生成100个Math.random()的值并画成散点图,然后对比crypto.getRandomValues生成的Uint32Array。前者的分布偶尔会出现肉眼可见的条纹(取决于算法实现),后者在任何尺度下都是均匀噪点。

五、权威标准怎么说

关于随机数生成在密码学中的应用,有几个公认的权威参考:

  • NIST SP 800-90A:美国国家标准与技术研究院对确定性随机比特生成器的推荐标准,我们使用的 crypto.getRandomValues 底层实现就遵循了这个框架。
  • OWASP(开放Web应用安全项目):在密码存储与生成的最佳实践中明确推荐使用CSPRNG,而非普通PRNG。
  • RFC 4086(Randomness Requirements for Security):详细讨论了密码学场景下对随机性的要求,包括熵源质量的评估方法。

这些标准看起来有点远,但它们共同画了一条线:凡是涉及密码、密钥、令牌生成的场景,别碰Math.random

总结

密码的安全性,归根结底取决于两个东西:长度和随机性。长度你可以自己选,随机性则由算法和熵源决定。用对了工具,16位混合密码就足以让当前的任何计算机在合理时间内无法穷举破解;但用错了随机源,再长的密码也可能因为可预测而形同虚设。

我们选择 crypto.getRandomValues 并不是因为它看起来高级,而是因为在这个位置上,任何一个负责任的设计都没有第二种选择。如果你对具体的实现细节感兴趣,随时可以打开页面源码查看——没有黑盒,没有后端,一切都摊开在浏览器里。

用真随机算法生成你的强密码

基于 crypto.getRandomValues 加密级随机数,16-64位,本地生成不泄露

立即生成 →