计算器

随机数生成详解:PRNG vs TRNG 及其工作原理

深入了解随机数生成器的工作原理,真随机数生成器(TRNG)与伪随机数生成器(PRNG)的区别、熵源、以及从密码学到蒙特卡洛模拟的实际应用。

发布于 2026年3月19日
14 分钟阅读
密码学指南

简介

随机数无处不在,即便你从未意识到这一点。每次你随机播放歌单、抽取彩票号码、加密消息、运行科学模拟或看到验证码,随机数生成器都在幕后默默工作。全球网络安全行业依赖随机性来生成加密密钥,每天保护着数万亿美元的交易安全。

然而这里有一个悖论:计算机是确定性机器。它们以精确、可重复的顺序执行指令。那么,一台确定性机器是如何产生不可预测的结果的呢?

答案涉及数学、物理学和工程学的精妙融合。有些生成器从物理世界中采集随机性,有些生成器利用巧妙的算法生成看似随机的序列,还有一些混合生成器将两种方式结合,以满足密码学安全需求。

本指南将解释随机数生成器的工作原理、真随机数生成与伪随机数生成的关键区别、随机性种子(seed)所需的熵(entropy)从何而来、实际应用场景、曝光随机数系统缺陷的著名失败案例,以及实用编程示例。

试用我们的免费随机数生成器,即刻以自定义范围、数量和不重复选项生成随机数。


什么是随机数生成器?

随机数生成器(RNG)是任何能产生无规律可循的数字序列的过程或设备。在理想的随机数生成器中,输出序列中的每个数字在统计上独立于其他所有数字,且范围内每个可能的值被选中的概率相等。

实际上,「随机性质量」的优劣取决于所使用的方法,存在一个连续谱:

  • 真随机数生成器(TRNG):从物理现象中产生真正不可预测的数字
  • 伪随机数生成器(PRNG):产生确定性序列,看起来随机,但在给定相同初始条件时完全可重现
  • 密码学安全 PRNG(CSPRNG):PRNG 的一个子集,设计上即使攻击者知道算法,从计算上预测其输出也不可行

选择哪种类型完全取决于应用场景。视频游戏需要速度快、够用的随机性;加密系统需要计算上不可预测的随机性;物理模拟则可能需要可重现的随机性以便验证。


真随机数生成器(TRNG)

真随机数生成器从根本上不可预测的物理现象中获取输出,也称为硬件随机数生成器。

常见熵源

热噪声: 电子元件因绝对零度以上温度下电子的随机运动而产生微小的电压波动。这些波动在量子层面是真正随机的,可被放大并数字化。

大气噪声: 调谐在电台之间的无线电接收器会接收到来自雷击和其他自然现象的大气电磁噪声。random.org 服务就利用大气噪声来生成随机数。

放射性衰变: 放射性衰变事件的时间由量子力学支配,从根本上是不可预测的。盖革计数器的点击可以转化为随机比特。

光子行为: 当单个光子照射到分束器上时,量子力学表明它有 50/50 的概率被透射或反射,从而以高速产生真正随机的比特。

时钟抖动: CPU 时钟时序的微小变化——由热效应和电气噪声引起——在任何计算机上都提供了可用的熵源。

优势与局限

优势:

  • 产生真正不可预测的输出
  • 原则上无法重现或预测
  • 适合最高安全级别的应用

局限:

  • 通常比算法方法慢
  • 需要专用硬件
  • 输出速率受物理过程限制
  • 原始输出可能存在轻微偏差,需要后处理

伪随机数生成器(PRNG)

伪随机数生成器使用确定性数学算法生成能通过随机性统计检验的数字序列。尽管完全可由初始状态预测,设计良好的 PRNG 对于大多数应用来说,其输出在实践中与真随机性无法区分。

PRNG 的工作原理

每个 PRNG 都遵循相同的基本模式:

  1. 种子(Seed): 初始值(种子)设定生成器的内部状态
  2. 算法: 数学函数将当前状态转换为下一个状态并产生输出数字
  3. 循环: 过程持续进行,每个状态产生下一个状态

相同的种子始终产生相同的序列。这对于模拟和测试的可重现性实际上非常有用。

常见 PRNG 算法

线性同余生成器(LCG): 最古老、最简单的 PRNG 之一,使用公式:next = (a * current + c) mod m。速度快,但存在已知弱点,包括周期(period)短和低位比特的可预测模式。C 标准库的 rand() 传统上使用 LCG。

梅森旋转算法(Mersenne Twister,MT19937): 使用最广泛的通用 PRNG,周期(period)为 2^19937 - 1(一个有 6,002 位数字的数),通过了大多数统计检验,是 Python、Ruby、R、MATLAB 和许多其他语言的默认 PRNG。然而,它不具备密码学安全性——观测 624 个连续输出即可完全预测所有未来输出。

Xorshift 系列(xoshiro、xoroshiro): 现代、快速的 PRNG,使用按位异或(XOR)和移位操作。统计质量出色,速度极快,因此在游戏和模拟中广受欢迎。由 Sebastiano Vigna 和 David Blackman 开发。

PCG(置换同余生成器): 由 Melissa O'Neill 设计的一系列 PRNG,将 LCG 与置换函数相结合。PCG 生成器速度快、统计特性好,且状态尺寸相对较小。

PRNG 适用的场景

当你需要以下特性时,PRNG 是合适的选择:

  • 速度(每秒数百万或数十亿个数字)
  • 可重现性(相同种子 = 相同序列)
  • 无需密码学安全性的统计质量
  • 模拟、游戏、蒙特卡洛方法或统计采样

密码学安全 PRNG(CSPRNG)

CSPRNG 弥合了真随机性与算法速度之间的差距。它是一种带有额外安全保证的 PRNG,使其适合密码学应用。

安全性要求

CSPRNG 必须满足两个属性:

  1. 下一比特检验: 给定随机序列的前 k 个比特,不存在多项式时间算法能以显著大于 50% 的概率预测第 k+1 个比特。

  2. 状态泄露扩展抗性: 即使攻击者获知了生成器当前的内部状态,也无法确定之前的输出(后向安全性)。

CSPRNG 如何获取熵

CSPRNG 从多个硬件来源收集真随机性作为种子(seed):

  • 键盘和鼠标时序: 用户输入时序的微秒级变化
  • 磁盘 I/O 时序: 硬盘寻道时间和固态硬盘访问模式的变化
  • 网络数据包到达时间: 网络流量中的时序抖动
  • CPU 指令时序: 缓存效应和分支预测引起的变化
  • 硬件随机数指令: 现代 CPU(Intel RDRAND、AMD)内置专用硬件熵源
  • 中断时序: 硬件中断发生时间的变化

操作系统将这些熵(entropy)收集到一个「熵池」中,为 CSPRNG 提供种子。在 Linux 上,这是 /dev/urandom;在 Windows 上,是 CryptGenRandom;在浏览器中,Web Crypto API 的 crypto.getRandomValues() 访问操作系统的熵池。

常见 CSPRNG

AES-CTR DRBG: 使用计数器模式的 AES 分组密码作为 PRNG,是 NIST 推荐的默认方案,部署广泛。

ChaCha20: 由 Daniel J. Bernstein 设计的流密码,被 Linux(内核 4.8 起)、FreeBSD 和许多应用程序用作 CSPRNG。它在软件上速度快,并能抵抗时序旁路攻击。

Fortuna: 由 Bruce Schneier 和 Niels Ferguson 设计,Fortuna 使用多个熵池并自动从系统熵中重新播种,被 macOS 和 iOS 采用。


计算机如何生成随机数

让我们追踪从物理熵到屏幕上显示的随机数的完整路径。

熵的传递流程

  1. 硬件事件产生原始熵(按键、鼠标移动、磁盘时序、CPU 硬件随机数的热噪声)
  2. 操作系统熵池从多个来源收集并混合熵,维护可用随机性的估计值
  3. CSPRNG 从熵池获取种子,利用密码学算法将少量真随机性扩展成大量密码学安全的伪随机比特流
  4. 应用程序调用系统的随机 API,以所需格式和范围获取数字

种子值

种子(seed)是启动 PRNG 的初始状态。种子的质量直接决定输出的质量:

  • 劣质种子: 使用当前时间作为种子(旧系统中常见)意味着,任何大致知道生成器启动时间的人都能大幅缩小种子空间
  • 优质种子: 来自 CSPRNG 的 256 位值提供 2^256 种可能的初始状态——比可观测宇宙中的原子数量还多
  • 可重现种子: 在科学计算中,刻意选择固定种子可以精确重现实验,对调试和验证非常有价值

均匀分布

大多数随机数生成器产生均匀分布(uniform distribution)的整数——范围内每个值被选中的概率相等。转换为其他分布需要额外的数学计算:

  • 正态(高斯)分布: Box-Muller 变换将两个均匀随机数转换为两个正态分布随机数
  • 指数分布: 应用反函数 CDF:-ln(1 - U) / lambda,其中 U 为 [0,1) 上的均匀分布随机数
  • 自定义分布: 使用反变换法或拒绝采样

随机数的应用

游戏

视频游戏大量使用随机数:战利品掉落率、暴击概率、程序化世界生成、AI 决策、洗牌、掷骰子和刷新点选择。大多数游戏使用 xoshiro256** 等快速 PRNG,因为速度比密码学安全性更重要。

密码学

随机性是现代密码学的基础,以下方面都需要随机数:

  • 加密密钥: AES-256 需要真正随机的 256 位密钥
  • 初始化向量(IV): 防止相同明文产生相同密文
  • 随机数(Nonce): 认证协议中使用的一次性值
  • 盐值(Salt): 哈希前添加到密码中的随机数据
  • 会话令牌: 用于 Web 会话的不可预测标识符

用于密码学的随机数生成器中的任何弱点都可能危及整个安全系统。

统计与科学

  • 蒙特卡洛模拟: 通过随机采样估计复杂的数学量(用于物理、金融、工程领域)
  • 自助法重采样: 通过有放回地重采样数据来估计置信区间的统计技术
  • 随机对照试验: 将参与者随机分配到实验组和对照组
  • 随机建模: 天气预报、流行病学和金融市场都使用随机数生成

彩票与博彩合规

合法的彩票系统需要满足严格标准的认证随机数生成器。大多数司法管辖区要求使用硬件 TRNG 或经过全面测试和审计的 CSPRNG。测试通常包括让生成器通过统计测试套件(NIST SP 800-22、Dieharder、TestU01),验证输出中不存在可检测的规律。


著名随机性失败案例

历史表明,随机性出错可能带来严重后果。

Dual_EC_DRBG:NSA 后门事件

2006 年,NIST 将 Dual_EC_DRBG(双椭圆曲线确定性随机比特生成器)标准化为经认可的 CSPRNG。密码学家很快发现它速度慢得可疑,并包含一些不明来源的常数,这些常数可能充当后门。2013 年,爱德华·斯诺登的文件证实 NSA 蓄意植入了后门,使其能够预测任何使用默认常数系统的输出。RSA Security 曾将其作为 BSAFE 工具包的默认选项,NIST 于 2014 年撤销了该标准。

教训: 算法设计的透明度至关重要。常数应有可验证的、来源清白的出处。

PHP 的 rand() 和 mt_rand()

PHP 的 rand() 函数历史上使用状态较小的弱 LCG,使其极易预测。替代品 mt_rand()(梅森旋转算法)改善了统计质量,但仍不具备密码学安全性。攻击者利用 mt_rand() 在 Web 应用中预测会话令牌、CSRF 令牌和密码重置链接。观察到足够多的输出后,内部状态可被完全还原。

教训: 绝不要将通用 PRNG 用于安全敏感应用。在 PHP 中使用 random_int()random_bytes(),它们使用操作系统的 CSPRNG。

Debian OpenSSL 漏洞(2008 年)

2006 年,一位 Debian 维护者注释掉了 OpenSSL 随机数生成器中的两行代码,因为代码分析工具将它们标记为使用了未初始化内存。而那两行代码实际上是主要的熵源。在此后整整两年里,Debian 和 Ubuntu 系统上生成的每一个 SSL 密钥、SSH 密钥及其他密码学密钥,其可能的值至多只有 32,768 种,而非原本天文数字般的数量。这段时间内生成的任何密钥都可以在几秒内被猜出。

教训: 对密码学代码的修改需要专家审查。自动化工具可能将合法行为标记为漏洞。

PlayStation 3 ECDSA 破解事件(2010 年)

索尼 PlayStation 3 的代码签名使用了 ECDSA(椭圆曲线数字签名算法),该算法要求每次签名都使用全新的随机数(nonce)。而索尼对每次签名都使用了相同的随机数。这一数学错误使黑客能够恢复索尼的私有签名密钥,从而在任何 PS3 上安装未授权软件。

教训: 在密码学协议中重复使用随机值可能造成灾难性后果。随机数(Nonce)必须唯一。

RANDU 灾难(20 世纪 60 年代)

IBM 的 RANDU 生成器在整个 20 世纪 60 至 70 年代被科学计算领域广泛使用,却存在严重缺陷:将其输出在三维空间中绘图时,结果仅落在 15 个平行平面上,而非均匀填充整个空间。这一时期发表的无数科学模拟和研究论文都使用了 RANDU,其结果可能因这种非随机性而受到影响。由于该生成器分布极广,部分受影响的研究始终未被识别或纠正。

教训: 对随机数生成器进行统计检验至关重要。可视化检查(多维绘图)能揭示简单的一维检验所遗漏的规律。


编程中的随机数生成

JavaScript

// 生成 min 到 max 之间的随机整数(含端点)
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(getRandomInt(1, 100)); // 例如:42

// 密码学安全的随机整数
function getSecureRandomInt(min, max) {
  const range = max - min + 1;
  const bytesNeeded = Math.ceil(Math.log2(range) / 8);
  const maxValid = Math.floor(256 ** bytesNeeded / range) * range - 1;
  let value;
  do {
    const array = new Uint8Array(bytesNeeded);
    crypto.getRandomValues(array);
    value = array.reduce((acc, byte, i) => acc + byte * (256 ** i), 0);
  } while (value > maxValid);
  return min + (value % range);
}

// 生成不重复随机数数组
function getUniqueRandomNumbers(min, max, count) {
  const range = Array.from({ length: max - min + 1 }, (_, i) => min + i);
  // Fisher-Yates 洗牌
  for (let i = range.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [range[i], range[j]] = [range[j], range[i]];
  }
  return range.slice(0, count);
}
console.log(getUniqueRandomNumbers(1, 49, 6)); // 例如:[7, 23, 41, 3, 38, 15]

// 模拟掷骰子
function rollDice(sides = 6, count = 1) {
  return Array.from({ length: count }, () => getRandomInt(1, sides));
}
console.log(rollDice(6, 3)); // 例如:[4, 2, 6]

Python

import random
import secrets

# 生成 1 到 100 之间的随机整数
print(random.randint(1, 100))  # 例如:42

# 密码学安全的随机整数
print(secrets.randbelow(100) + 1)  # 1 到 100

# 生成不重复随机数
lottery = random.sample(range(1, 50), 6)
print(sorted(lottery))  # 例如:[3, 7, 15, 23, 38, 41]

# 使用种子生成可重现的随机序列
random.seed(42)
print([random.randint(1, 100) for _ in range(5)])
# 始终为:[82, 15, 4, 95, 36]

# 原地打乱列表
deck = list(range(1, 53))
random.shuffle(deck)
print(deck[:5])  # 例如:[37, 12, 49, 3, 28]

# 加权随机选择
choices = ['common', 'uncommon', 'rare', 'legendary']
weights = [70, 20, 8, 2]
result = random.choices(choices, weights=weights, k=10)
print(result)

# 密码学安全的令牌
token = secrets.token_hex(32)  # 64 字符的十六进制字符串
print(token)

# 安全随机密码
import string
alphabet = string.ascii_letters + string.digits + string.punctuation
password = ''.join(secrets.choice(alphabet) for _ in range(16))
print(password)

各语言关键差异

特性JavaScriptPython
基础 PRNGMath.random()random.random()
整数范围Math.floor(Math.random() * n)random.randint(a, b)
密码学安全crypto.getRandomValues()secrets.token_bytes()
可播种否(Math.random)是(random.seed()
不重复采样手动 Fisher-Yatesrandom.sample()

概率与公平性

均匀分布

公平的随机数生成器产生均匀分布(uniform distribution):每个可能的值被选中的概率完全相同。对于 1-100 的范围,在大量抽取中,每个数字出现的频率应约为 1%。

随机性检验

如何验证生成器实际上产生了良好的随机数?目前存在几种标准测试套件:

NIST SP 800-22: 美国国家标准与技术研究院的测试套件包含 15 项统计检验:频率检验、游程检验、最长游程检验、FFT 检验、序列检验等。用于认证 CSPRNG。

Dieharder: 由杜克大学 Robert G. Brown 开发的综合套件,包含 31 项以上统计检验,涵盖生日间距、重叠排列、停车场检验和挤压检验等。

TestU01: 由蒙特利尔大学 Pierre L'Ecuyer 开发,TestU01 包含「Big Crush」测试集,共 160 项统计检验。它被认为是目前最严格的通用 PRNG 测试套件。

生日悖论与碰撞

生成随机数时,碰撞(重复值)出现的速度往往比大多数人预期的更快。在仅有 23 人的群体中,有两人同一天生日的概率高达 50%(365 个可能日期中)。类似地:

  • 生成 32 位随机整数时,大约 77,000 个数字后就会出现碰撞
  • 对于 64 位整数,大约 50 亿个数字后出现碰撞
  • 对于 128 位值,大约 2^64(18 亿亿)个值后出现碰撞

这对于生成唯一标识符非常重要。UUID(128 位)足够大,碰撞在实践中几乎不可能发生,但较短的随机 ID 可能比预期更早发生碰撞。

随机数生成中的偏差

将 PRNG 的输出映射到特定范围时,会出现一个微妙问题。如果 PRNG 产生 0-255 的值,而你需要 1-100 的数字,使用 value % 100 + 1 会引入轻微偏差:1-56 的值比 57-100 略多,因为 256 不能被 100 整除。正确的方法是拒绝采样:丢弃会导致偏差的值并重新生成。


常见问题

JavaScript 中的 Math.random() 适合用来生成密码吗?

不适合。Math.random() 是一个快速 PRNG(现代引擎中通常是 xorshift128+),不具备密码学安全性,其内部状态可以从观测到的输出中恢复。对于密码、令牌或任何安全目的,请使用 crypto.getRandomValues() 或 Web Crypto API。

老虎机如何生成随机结果?

现代老虎机使用经认证的 CSPRNG,持续生成随机数(通常每秒数千个),即使无人游玩时也不停止。玩家按下旋转按钮的精确时刻决定使用哪个随机数,该数字映射到特定的转轮配置。博彩委员会对这些随机数生成器进行测试和认证,以确保公平性。

量子计算机能破解随机数生成器吗?

量子计算机无法破解 TRNG,因为来自物理现象的真随机性无论计算能力多强都不可预测。然而,如果底层密码学原语(如 AES 或椭圆曲线)变得脆弱,量子计算机理论上可以破解某些 CSPRNG。抗量子 CSPRNG 设计正在研发中以应对这一威胁。

Math.random() 和 crypto.getRandomValues() 有什么区别?

Math.random() 使用快速但不安全的算法返回 0 到 1 之间的浮点数,适合游戏和模拟。crypto.getRandomValues() 将类型化数组填充为来自操作系统熵池的密码学强随机值,适合安全敏感应用,但速度稍慢。

如何生成正态分布(钟形曲线)的随机数?

使用 Box-Muller 变换:生成两个在 0 到 1 之间的均匀随机数 U1 和 U2,然后计算 Z = sqrt(-2 * ln(U1)) * cos(2 * pi * U2)。Z 将服从标准正态分布(均值为 0,标准差为 1)。按需缩放和平移:result = mean + Z * standardDeviation

为什么绝对不能用当前时间作为安全随机种子?

当前时间是可预测的。知道密钥大概生成时间的攻击者可以尝试该时间窗口内所有可能的时间戳。即使精确到毫秒,每天也只有约 86,400,000 种可能的种子——现代计算机可以轻松穷举。安全敏感应用必须通过操作系统 CSPRNG 使用来自硬件的熵。

如何检验随机数生成器是否公平?

多次运行生成器(至少 10,000 次),检验结果分布是否与预期分布相符。对于生成 1-6 值的均匀生成器,每个值出现的频率应约为 1/6。应用卡方检验以验证统计均匀性。如需更严格的检验,可使用 NIST SP 800-22 或 TestU01 的 Big Crush 等标准化测试套件,它们应用了数十项统计检验,涵盖频率分析、序列相关、最长游程、频谱分析等。

关于本文

本文是我们综合 计算器 教程系列的一部分。继续了解古典密码学,并探索我们的交互式密码工具。

更多 计算器 教程

试用 计算器 工具

通过我们的交互式计算器工具,将所学知识付诸实践。

试用 计算器 工具