随机生成器:工作原理及使用场景

· 13分钟阅读

随机生成器无处不在。每次你随机播放歌单、生成密码、运行模拟或玩电子游戏时,随机数生成器都在幕后工作。但你是否想过,计算机——这种遵循精确指令的确定性机器——是如何产生随机性的?答案比你想象的更有趣(也更微妙)。

本指南探讨随机生成器背后的科学和工程原理。我们将介绍真随机数和伪随机数之间的区别、流行算法的工作原理、何时需要密码学级别的随机性,以及影响你日常生活的实际应用。无论你是选择合适随机函数的开发者、学习概率的学生,还是单纯对事物运作原理感到好奇,本指南都能满足你的需求。

什么是随机性?深入探讨

随机性看似直观——它是不可预测性、混沌、缺乏模式。但精确定义随机性却出乎意料地困难,数学家们已经争论了几个世纪。

从最严格的意义上说,如果没有算法能比猜测更好地预测下一个值,即使完全了解所有先前的值,那么这个序列就是真正随机的。这被称为柯尔莫哥洛夫随机性——如果一个序列无法被压缩为更短的描述,那么它就是随机的。例如,圆周率的数字通过了所有随机性统计测试,但它们是完全确定性的(你可以通过足够的计算来计算任何一位数字)。

在实际应用中,我们关心随机性的三个属性:

不同的应用需要这些属性的不同程度。掷骰子游戏需要均匀性和独立性,但不一定需要不可预测性。密码生成器需要全部三个属性。了解你的应用需要什么样的随机性是选择正确生成器的第一步。

真随机与伪随机:根本区别

所有随机生成器分为两类,这种区别对正确性和安全性都极为重要。

真随机数生成器(TRNG)

真随机生成器从根本上不可预测的物理现象中获取随机性:

TRNG的关键特性是其输出是不可重现的。即使使用相同的设备和条件,每次也会得到不同的序列。这对于密码学应用至关重要,因为可重现性将是致命缺陷。

伪随机数生成器(PRNG)

PRNG是算法——确定性数学公式,产生看似随机但在给定初始状态(称为种子)的情况下完全可预测的数字序列。给PRNG相同的种子,它每次都会产生完全相同的序列。

这听起来像是一个弱点,但实际上对许多应用来说是一个特性:

挑战在于设计输出在统计上与真随机性无法区分的PRNG——通过所有已知的均匀性、独立性和缺乏模式的统计测试。

🎲 试用我们的生成器

随机数生成器 → 密码生成器 →

伪随机数生成器(PRNG)的工作原理

从本质上讲,PRNG维护一个内部状态,每次调用时都会对其进行转换,产生输出并更新状态以供下次使用。PRNG的质量取决于其转换避免模式和相关性的能力。

线性同余生成器(LCG)

最简单和最古老的PRNG系列,LCG使用以下公式:

state = (a × state + c) mod m
output = state

其中a(乘数)、c(增量)和m(模数)是精心选择的常数。例如,经典的MINSTD生成器使用a = 16807c = 0m = 2³¹ - 1。LCG速度极快(一次乘法和一次取模),但有众所周知的弱点:在高维度绘制时,连续值显示模式,低位比特以短周期循环。大多数现代应用已经超越了LCG。

梅森旋转算法(MT19937)

自1997年推出以来,梅森旋转算法一直是许多语言和系统中的默认PRNG。它维护624个32位整数的状态,周期为2¹⁹⁹³⁷ - 1(一个梅森素数,因此得名)。这个周期长得惊人——远超过可观测宇宙中的原子数量。

该算法的工作原理是:

  1. 从种子初始化624个状态值
  2. 对于每个随机数,通过位移和异或运算提取并"调和"一个状态值
  3. 定期使用扭曲变换重新生成整个状态数组

梅森旋转算法通过了大多数统计测试,非常适合模拟、游戏和通用用途。然而,它不是密码学安全的——观察624个输出就可以重建完整的内部状态并预测所有未来值。

Xoshiro256**和现代PRNG

像xoshiro256**(用于许多现代语言)和PCG(置换同余生成器)这样的新型PRNG解决了旧生成器的缺点。它们提供:

JavaScript的Math.random()

当你在JavaScript中调用Math.random()时,现代引擎(V8、SpiderMonkey、JavaScriptCore)使用xorshift128+,这是一个具有良好统计特性的快速PRNG。它产生一个介于0(包含)和1(不包含)之间的浮点数。虽然对于游戏、随机播放和随机选择来说完全没问题,但它绝不应该用于安全敏感的目的。

// min和max之间的基本随机数(包含)
function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

// 从数组中随机选择元素
function randomChoice(array) {
  return array[Math.floor(Math.random() * array.length)];
}

// 打乱数组(Fisher-Yates算法)
function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}

密码学随机性:安全至关重要时

对于安全应用——密码、加密密钥、会话令牌、随机数——常规PRNG是危险地不足的。能够预测你的随机数的攻击者可以破坏你的整个安全模型。

CSPRNG有什么不同?

密码学安全伪随机数生成器(CSPRNG)增加了一个关键属性:前向和后向保密性。即使攻击者发现当前的内部状态,他们也无法确定先前的输出(后向保密性)。在不知道状态的情况下,从过去的输出预测未来的输出在计算上是不可行的。

CSPRNG通过以下方式实现这一点:

常见的CSPRNG

在实践中使用密码学随机性

在浏览器中,Web Crypto API提供密码学级别的随机性:

// 生成密码学安全的随机整数
function secureRandomInt(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 generatePassword(length = 16) {
  const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
  const array = new Uint32Array(length);
  crypto.getRandomValues(array);
  return Array.from(array, x => chars[x % chars.length]).join('');
}

使用我们的密码生成器尝试生成安全密码,它使用Web Crypto API提供密码学级别的随机性。

熵:随机性的燃料

在随机性的背景下,熵衡量系统中真正不可预测性的数量。它以比特为单位测量——一比特熵意味着攻击者无法预测的一个二进制选择。

熵从哪里来?

操作系统持续从多个来源收集熵:

We use cookies for analytics. By continuing, you agree to our Privacy Policy.