第二十七 随机数
随机数概述
随机性是计算机科学中一个基础而深刻的概念。从蒙特卡洛模拟到密码学协议,从游戏引擎到机器学习,随机数无处不在。然而,计算机本质上是确定性的机器——给定相同的输入,它总是产生相同的输出。那么,计算机如何产生“随机“的数字呢?
这正是随机数生成理论要解决的核心问题。理解随机数的本质、分类和生成原理,对于编写安全、可靠的软件至关重要。一个看似微不足道的随机数生成器缺陷,可能导致加密系统被完全攻破,或导致模拟实验得出错误的结论。
随机数的三个标准
根据密码学原理,随机数的随机性检验可以分为三个标准: [1]
-
统计学伪随机性。统计学伪随机性指的是在给定的随机比特流样本中,1的数量大致等于0的数量,同理,“10”“01”“00”“11“四者数量大致相等。类似的标准被称为统计学随机性。满足这类要求的数字在人类“一眼看上去“是随机的。更严格地说,统计学伪随机性要求比特流能够通过一系列统计检验,包括频率检验、游程检验、频谱检验等。数学上,一个理想的随机比特流中每个比特位为1的概率为 $p = 0.5$,且各比特位之间相互独立。
-
密码学安全伪随机性。其定义为,给定随机样本的一部分和随机算法,不能有效的演算出随机样本的剩余部分。这意味着即使攻击者观察到了生成器输出的任意长前缀,也无法以显著优于随机猜测的概率预测下一个输出比特。形式化表述为:对于任意多项式时间算法 $A$,预测下一个比特的成功概率与 $1/2$ 的差值可忽略不计:
$$|\Pr[A(x_1, x_2, \ldots, x_n) = x_{n+1}] - \frac{1}{2}| \leq \text{negl}(n)$$
- 真随机性。其定义为随机样本不可重现。实际上只要给定边界条件,真随机数并不存在,可是如果产生一个真随机数样本的边界条件十分复杂且难以捕捉(比如计算机当地的本底辐射波动值),可以认为用这个方法演算出来了真随机数。真随机数的核心特征是不可预测性和不可重现性——即使完全了解生成机制,也无法在事先预测输出结果。
随机数分类
相应的,随机数也分为三类:
| 分类 | 满足的标准 | 生成方式 | 典型用途 |
|---|---|---|---|
| 伪随机数(PRNG) | 统计学伪随机性 | 确定性算法 + 种子 | 模拟、游戏、测试 |
| 密码学安全伪随机数(CSPRNG) | 统计学 + 密码学安全伪随机性 | 密码学算法 + 熵源 | 密钥生成、nonce、salt |
| 真随机数(TRNG) | 全部三个标准 | 物理现象 | 高安全场景、种子生成 |
- 伪随机数:满足第一个条件的随机数。由确定性算法生成,给定相同的种子(seed),将产生完全相同的序列。虽然不具备密码学安全性,但生成速度快、可重现,适用于不需要安全保证的场景。
- 密码学安全的伪随机数:同时满足前两个条件的随机数。可以通过密码学安全伪随机数生成器计算得出。即使攻击者获取了部分输出,也无法推断出之前或之后的输出。
- 真随机数:同时满足三个条件的随机数。来源于物理随机过程,不可预测也不可重现。
随机数在密码学中非常重要,保密通信中大量运用的会话密钥的生成即需要真随机数的参与。如果一个随机数生成算法是有缺陷的,那么会话密钥可以直接被推算出来。若果真发生这种事故,那么任何加密算法都失去了意义。
随机数分为伪随机数和真随机数。伪随机数又分为弱伪随机数和强伪随机数。
随机数生成器
随机数生成器有两种类型:真正的随机数生成器和伪随机数生成器。
伪随机数生成器原理
伪随机数生成器(PRNG)通过确定性算法从初始种子(seed)生成看似随机的数列。其核心思想是:用一个确定性的递推公式,将当前状态映射到下一个状态,同时输出一个(经过变换的)伪随机数。
线性同余法(LCG)
线性同余法(Linear Congruential Generator)是最经典的伪随机数生成算法,由 Lehmer 于 1949 年提出。其递推公式为:
$$x_{n+1} = (a \cdot x_n + c) \mod m$$
其中:
- $x_n$ 为当前状态(种子)
- $a$ 为乘数(multiplier)
- $c$ 为增量(increment)
- $m$ 为模数(modulus)
LCG 的最大周期为 $m$。要达到最大周期,需满足 Hull-Dobell 定理:
- $c$ 与 $m$ 互质($\gcd(c, m) = 1$)
- $a - 1$ 能被 $m$ 的所有质因子整除
- 若 $m$ 是 4 的倍数,则 $a - 1$ 也是 4 的倍数
/// 线性同余法(LCG)的简单实现
struct Lcg {
state: u64,
a: u64,
c: u64,
m: u64,
}
impl Lcg {
fn new(seed: u64, a: u64, c: u64, m: u64) -> Self {
Self { state: seed, a, c, m }
}
fn next(&mut self) -> u64 {
// x_{n+1} = (a * x_n + c) mod m
self.state = (self.a.wrapping_mul(self.state).wrapping_add(self.c)) % self.m;
self.state
}
}
fn main() {
// 使用经典参数:glibc 使用的参数
let mut lcg = Lcg::new(42, 1103515245, 12345, 1 << 31);
for _ in 0..10 {
println!("{}", lcg.next());
}
}
LCG 的优点是实现简单、速度快,但缺点也很明显:低位比特的随机性较差,状态空间有限容易被预测,不适合密码学用途。著名的案例是 1994 年 Netscape 浏览器使用 LCG 生成 SSL 密钥,被攻击者成功破解。
梅森旋转器(Mersenne Twister)
梅森旋转器是目前应用最广泛的通用伪随机数生成器之一,由松本真和西村拓士于 1997 年提出。其名称来源于其周期长度——梅森素数 $2^{19937}-1$。
核心特点:
- 超长周期:$2^{19937}-1 \approx 4.3 \times 10^{6001}$,远超任何实际应用需求
- 高维均匀分布:在高达 623 维的空间上均匀分布
- 快速:多数平台上每生成一个 32 位随机数仅需几纳秒
- 通过多数统计测试:Diehard 和大部分 NIST 测试
// 注意:rand 0.9+ 的 StdRng 实际使用 ChaCha8 算法,而非梅森旋转器
// 如需使用梅森旋转器,需要额外的 rand_mt crate
use rand::RngExt;
fn main() {
// rand::rng() 默认使用 ThreadRng,内部基于 ChaCha8
let mut rng = rand::rngs::StdRng::from_seed([42u8; 32]);
for _ in 0..5 {
println!("{}", rng.random::<u64>());
}
}
PCG / Xoshiro 算法
近年来,PCG(Permuted Congruential Generator)和 Xoshiro 系列算法因其出色的统计质量和性能受到广泛关注。
PCG 由 M.E. O’Neill 于 2014 年提出,基于 LCG 但增加了输出置换(output permutation),显著改善了统计特性:
$$x_{n+1} = (a \cdot x_n + c) \mod 2^n$$ $$\text{output} = \text{rotate}(x_{n+1} \oplus (x_{n+1} >> r), x_{n+1} >> s)$$
Xoshiro 系列由 Sebastiano Vigna 提出,基于 XOR-shift/rotate 操作,速度极快且统计质量优秀。
use rand::RngExt;
fn main() {
// 使用 StdRng(ChaCha8 算法)
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
println!("StdRng (ChaCha8): {}", rng.random::<u64>());
// 使用 SmallRng(平台优化的快速生成器)
let mut rng = rand::rngs::SmallRng::seed_from_u64(42);
println!("SmallRng: {}", rng.random::<u64>());
}
算法对比
| 算法 | 周期 | 状态大小 | 速度 | 统计质量 | 密码学安全 | 适用场景 |
|---|---|---|---|---|---|---|
| LCG | $\leq m$(通常 $2^{32}$) | 4-8 字节 | 极快 | 差 | 否 | 简单模拟、教学 |
| Mersenne Twister | $2^{19937}-1$ | 2500 字节 | 快 | 好 | 否 | 通用模拟、游戏 |
| SmallRng | 平台相关 | 16-32 字节 | 极快 | 优秀 | 否 | 通用高性能场景 |
| StdRng (ChaCha8) | $2^{64}$ | 32 字节 | 快 | 优秀 | 是 | 密码学用途 |
| ChaCha20 | $2^{64}$ | 32 字节 | 快 | 优秀 | 是 | 高安全密码学用途 |
真随机数来源
真随机数生成器(TRNG)利用物理世界的不可预测现象来产生随机数,不依赖确定性算法。
硬件随机数生成器(HRNG)
现代 CPU 和专用硬件通常内置了随机数生成器:
- Intel RDRAND/RDSEED 指令:利用芯片内部的热噪声生成随机数,自 2012 年(Ivy Bridge)起可用
- AMD RDRAND 指令:类似 Intel 的实现
- ARM RNDR 指令:ARMv8.5+ 引入的随机数指令
// ⚠️ 需要本地编译,不支持 Playground
// 使用 rdrand crate 访问硬件随机数生成器
// Cargo.toml: rdrand = "0.8"
// 注意:rdrand 依赖 CPU 硬件指令(Intel RDRAND/RDSEED),Playground 无法运行
use rand_core::TryRng;
use rdrand::RdRand;
fn main() {
if let Ok(mut rng) = RdRand::new() {
if let Ok(val) = rng.try_next_u64() {
println!("Hardware random: {}", val);
}
}
}
操作系统熵源
操作系统通过收集各种系统事件的时序信息来积累熵,提供高质量的随机数接口:
| 平台 | 接口 | 说明 |
|---|---|---|
| Linux / macOS | /dev/urandom | 非阻塞 CSPRNG,推荐使用 |
| Linux / macOS | /dev/random | 早期为阻塞接口,现代内核已与 urandom 等价 |
| Windows | CryptGenRandom / BCryptGenRandom | Windows CryptoAPI 提供的 CSPRNG |
| Windows | ProcessPrng | Windows 10+ 推荐接口 |
// 使用 rand::rng() 获取密码学安全的线程本地 RNG
// 在 rand 0.9 中,ThreadRng 使用 ChaCha12 算法,具有密码学安全性
use rand::RngExt;
fn main() {
let mut rng = rand::rng();
let mut buf = [0u8; 32];
rng.fill(&mut buf);
println!("OS random bytes: {:02x?}", buf);
// 也可以直接生成单个值
let random_u64: u64 = rng.random();
println!("Random u64: {}", random_u64);
}
物理现象
真随机数的物理来源包括:
- 热噪声(Johnson-Nyquist 噪声):电阻中电子的热运动产生的电压波动,服从高斯分布
- 放射性衰变:原子核衰变时刻的量子随机性
- 光电效应:光子到达探测器的随机时间
- 量子力学现象:量子叠加态的坍缩本质上是随机的
这些物理现象的共同特点是:基于量子力学的不确定性原理,其结果在原理上不可预测。
Rust 中的随机数
Rust 生态中随机数的核心 crate 是 rand,它提供了丰富的随机数生成功能。rand 0.9+ 版本进行了重大重构,API 更加现代化。
rand crate 基础用法
// ✅ 正确方式
use rand::RngExt;
fn main() {
let x: u8 = rand::random();
println!("{}", x);
}
// import commonly used items from the prelude:
use rand::RngExt;
use rand::seq::IteratorRandom;
use rand::prelude::SliceRandom;
fn main() {
// We can use random() immediately. It can produce values of many common types:
let x: u8 = rand::random();
println!("{}", x);
if rand::random() { // generates a boolean
println!("Heads!");
}
// If we want to be a bit more explicit (and a little more efficient) we can
// make a handle to the thread-local generator:
let mut rng = rand::rng();
if rng.random() { // random bool
let x: f64 = rng.random(); // random number in range [0, 1)
let y = rng.random_range(-10.0..10.0);
println!("x is: {}", x);
println!("y is: {}", y);
}
println!("Dice roll: {}", rng.random_range(1..=6));
println!("Number from 0 to 9: {}", rng.random_range(0..10));
// Sometimes it's useful to use distributions directly:
let distr = rand::distr::Uniform::new_inclusive(1, 100).unwrap();
let mut nums = [0i32; 3];
for x in &mut nums {
*x = rng.sample(distr);
}
println!("Some numbers: {:?}", nums);
// We can also interact with iterators and slices:
let arrows_iter = "➡⬈⬆⬉⬅⬋⬇⬊".chars();
println!("Lets go in this direction: {}", arrows_iter.choose(&mut rng).unwrap());
let mut nums = [1, 2, 3, 4, 5];
nums.shuffle(&mut rng);
println!("I shuffled my {:?}", nums);
}
#![allow(unused)]
fn main() {
// 以下为代码片段,非完整程序
//生成随机字节数组
use rand::RngExt;
let mut rng = rand::rng();
let mut block: [u8; 16] = [0; 16];
rng.fill(&mut block);
}
ThreadRng 与线程本地生成器
rand::rng() 返回一个线程本地的随机数生成器(ThreadRng),它是懒初始化的,每个线程拥有独立的实例,无需加锁,性能优异。
use rand::RngExt;
fn main() {
let mut rng = rand::rng();
// 生成各种基本类型
let a: u8 = rng.random();
let b: u16 = rng.random();
let c: u32 = rng.random();
let d: u64 = rng.random();
let e: f32 = rng.random(); // [0, 1)
let f: f64 = rng.random(); // [0, 1)
let g: bool = rng.random();
let h: char = rng.random(); // 随机 Unicode 字符
println!("u8={}, u16={}, u32={}, u64={}", a, b, c, d);
println!("f32={}, f64={}, bool={}, char={}", e, f, g, h);
}
各种概率分布
rand::distr 模块提供了丰富的概率分布:
// ⚠️ 需要 rand_distr 依赖
// ⚠️ Normal、Exp、LogNormal、Pareto 已从 rand 0.9 移至 rand_distr crate
// Playground 中需要添加依赖:rand_distr = "0.5"
use rand::RngExt;
use rand::distr::{Uniform, Bernoulli};
use rand_distr::{Normal, Exp, LogNormal, Pareto};
fn main() {
let mut rng = rand::rng();
// 均匀分布 [a, b]
let uniform = Uniform::new(1.0, 100.0).unwrap();
println!("Uniform: {}", rng.sample(uniform));
// 正态分布 N(μ, σ²),均值为0,标准差为1
let normal = Normal::new(0.0, 1.0).unwrap();
println!("Normal: {}", rng.sample(normal));
// 伯努利分布:以概率 p 返回 true
let bernoulli = Bernoulli::new(0.7).unwrap();
println!("Bernoulli: {}", rng.sample(bernoulli));
// 指数分布
let exp = Exp::new(2.0).unwrap();
println!("Exponential: {}", rng.sample(exp));
// 对数正态分布
let log_normal = LogNormal::new(0.0, 1.0).unwrap();
println!("LogNormal: {}", rng.sample(log_normal));
// 帕累托分布
let pareto = Pareto::new(1.0, 2.0).unwrap();
println!("Pareto: {}", rng.sample(pareto));
}
随机选择与洗牌
use rand::seq::{SliceRandom, IteratorRandom};
use rand::RngExt;
use rand::prelude::IndexedRandom;
fn main() {
let mut rng = rand::rng();
// 从切片中随机选择一个元素
let colors = ["red", "green", "blue", "yellow"];
let chosen = colors.choose(&mut rng).unwrap();
println!("Chosen color: {}", chosen);
// 随机选择多个不重复元素
let chosen_multiple = colors.choose_multiple(&mut rng, 2);
println!("Chosen 2 colors: {:?}", chosen_multiple);
// 从迭代器中随机选择
let chosen_from_iter = (1..=100).choose(&mut rng);
println!("Random number from 1..=100: {:?}", chosen_from_iter);
// 洗牌(Fisher-Yates 算法)
let mut deck = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
deck.shuffle(&mut rng);
println!("Shuffled deck: {:?}", deck);
// 部分洗牌:只打乱前 k 个位置
let mut cards = vec!["A", "B", "C", "D", "E"];
cards.partial_shuffle(&mut rng, 3);
println!("Top 3 random cards: {:?}", &cards[..3]);
}
自定义分布
通过实现 Distribution trait,可以创建自定义的概率分布:
use rand::distr::{Distribution, StandardUniform};
use rand::RngExt;
/// 自定义分布:掷两个骰子之和
struct DiceSum;
impl Distribution<u8> for DiceSum {
fn sample<R: RngExt + ?Sized>(&self, rng: &mut R) -> u8 {
let d1: u8 = rng.random_range(1..=6);
let d2: u8 = rng.random_range(1..=6);
d1 + d2
}
}
/// 为自定义类型实现随机生成
#[derive(Debug)]
enum Weather {
Sunny,
Cloudy,
Rainy,
Stormy,
}
impl Distribution<Weather> for StandardUniform {
fn sample<R: RngExt + ?Sized>(&self, rng: &mut R) -> Weather {
let val: f64 = rng.random();
match val {
v if v < 0.4 => Weather::Sunny,
v if v < 0.7 => Weather::Cloudy,
v if v < 0.9 => Weather::Rainy,
_ => Weather::Stormy,
}
}
}
fn main() {
let mut rng = rand::rng();
// 使用自定义 DiceSum 分布
let dice = DiceSum;
for _ in 0..10 {
println!("Dice sum: {}", rng.sample(&dice));
}
// 使用自定义 Weather 分布
for _ in 0..5 {
println!("Weather: {:?}", rng.random::<Weather>());
}
}
rand_core 和 OsRng
rand_core 是 rand 生态的基础抽象层,定义了核心 trait:
RngExt:用户层 trait,提供random()、random_range()、fill()、sample()等便捷方法SeedableRng:可从种子初始化的生成器 traitCryptoRng:标记 trait,表示生成器具有密码学安全性
use rand::{RngExt, CryptoRng};
fn generate_with_rng<R: RngExt + CryptoRng>(rng: &mut R) -> [u8; 32] {
let mut key = [0u8; 32];
rng.fill(&mut key);
key
}
fn main() {
// rand::rng() 返回的 ThreadRng 同时实现了 RngExt 和 CryptoRng
let mut rng = rand::rng();
let key = generate_with_rng(&mut rng);
println!("Crypto-safe key: {:02x?}", key);
}
getrandom crate
getrandom crate 提供了跨平台的系统随机数访问,是 rand 的底层依赖,也可以单独使用:
// ⚠️ getrandom 是独立 crate,Playground 中需要添加依赖
// Cargo.toml: getrandom = "0.3"
use getrandom::getrandom;
fn main() {
let mut buf = [0u8; 16];
getrandom(&mut buf).expect("failed to get random bytes");
println!("Random bytes: {:02x?}", buf);
}
getrandom 在不同平台上自动选择最佳熵源:
| 平台 | 熵源 |
|---|---|
| Linux | getrandom() 系统调用 |
| macOS | getentropy() |
| Windows | BCryptGenRandom |
| WebAssembly | Crypto.getRandomValues() |
| 嵌入式 | 可自定义实现 |
随机数在密码学中的应用
随机数在密码学中的用途主要有:生成nonce、生成salt、生成初始化向量、生成密钥(对称密钥或非对称密钥)。
- 随机数参与加密报文的签名;
- 随机数参与会话秘钥的生成;
生成 nonce、salt、IV、密钥
use rand::RngExt;
fn generate_crypto_params() {
let mut rng = rand::rng();
// 生成 12 字节 nonce(用于 AES-GCM)
let mut nonce = [0u8; 12];
rng.fill(&mut nonce);
println!("Nonce: {:02x?}", nonce);
// 生成 16 字节 salt(用于密钥派生)
let mut salt = [0u8; 16];
rng.fill(&mut salt);
println!("Salt: {:02x?}", salt);
// 生成 16 字节 IV(初始化向量,用于 AES-CBC)
let mut iv = [0u8; 16];
rng.fill(&mut iv);
println!("IV: {:02x?}", iv);
// 生成 32 字节对称密钥(AES-256)
let mut key = [0u8; 32];
rng.fill(&mut key);
println!("AES-256 Key: {:02x?}", key);
}
fn main() {
generate_crypto_params();
}
使用 openssl 命令生成随机数
openssl rand -hex 32
openssl rand –base64 32
openssl rand –base64 32 –out myr.dat
密钥生成的安全要求
密钥生成对随机数的质量有严格要求:
- 最小熵要求:128 位密钥至少需要 128 位熵。使用弱随机数生成器会导致密钥空间大幅缩小,使暴力破解成为可能。
- 不可预测性:密钥必须使用 CSPRNG 生成,绝不能使用普通 PRNG(如 LCG、Mersenne Twister)。
- 种子安全:CSPRNG 的种子本身必须来自真随机源(操作系统熵池或硬件 TRNG)。
- 避免重用:nonce 和 IV 必须保证不重复使用,否则会严重削弱加密安全性。
use rand::RngExt;
/// 安全地生成 RSA 密钥对所需的随机素数种子
fn generate_rsa_seed() -> [u8; 64] {
let mut seed = [0u8; 64];
rand::rng().fill(&mut seed);
seed
}
/// 生成一次性密码(OTP)的密钥
fn generate_otp_key() -> [u8; 20] {
let mut key = [0u8; 20]; // HOTP/TOTP 使用 20 字节(160 位)
rand::rng().fill(&mut key);
key
}
fn main() {
let rsa_seed = generate_rsa_seed();
println!("RSA seed: {:02x?}", rsa_seed);
let otp_key = generate_otp_key();
println!("OTP key: {:02x?}", otp_key);
}
一次性密码(One Time Password,简称OTP)
随机数测试
如何验证一个随机数生成器是否“足够随机“?这需要借助统计测试套件。
统计测试套件
常见的随机数测试套件包括:
| 测试套件 | 开发者 | 测试数量 | 说明 |
|---|---|---|---|
| Diehard | Marsaglia | 15 | 经典测试集,已过时 |
| Dieharder | Brown | 100+ | Diehard 的扩展版 |
| TestU01 | L’Ecuyer | 10+ | 包含 Big Crush、Small Crush |
| NIST SP 800-22 | NIST | 16 | 密码学标准测试 |
| PractRand | Sibidanov | 持续运行 | 最严格的测试之一 |
NIST SP 800-22 测试
NIST SP 800-22 是美国国家标准与技术研究院发布的随机数测试标准,包含 16 项测试:
- 频率(Frequency)测试:检验整个比特流中 0 和 1 的比例是否接近 0.5
- 块内频率(Block Frequency)测试:在 M 位块内检验频率
- 游程(Runs)测试:检验连续相同比特(游程)的数量分布
- 最长游程(Longest Run)测试:在一个块内检验最长游程
- 二元矩阵秩(Binary Matrix Rank)测试:检验固定大小矩阵的秩
- 离散傅里叶变换(FFT)测试:检测周期性模式
- 非重叠模板匹配(Non-overlapping Template)测试:检测特定比特模式的出现频率
- 重叠模板匹配(Overlapping Template)测试:类似但模板可重叠
- 通用统计(Universal Statistical)测试:基于 Maurer 的通用统计
- Lempel-Ziv 压缩(Linear Complexity)测试:检验线性复杂度
- 序列(Serial)测试:检验 $m$-bit 模式的频率
- 近似熵(Approximate Entropy)测试:评估序列的不可预测性
- 累积和(Cumulative Sums)测试:检测部分序列中 0 和 1 的偏向
- 随机偏移(Random Excursions)测试:检验随机游走特性
- 随机偏移变体(Random Excursions Variant)测试:随机游走的变体
- Maurer 通用统计(Maurer’s Universal)测试:评估信息存储能力
/// 简单的频率测试示例
fn frequency_test(bits: &[u8]) -> f64 {
let total_bits = bits.len() * 8;
let ones: usize = bits.iter().map(|b| b.count_ones() as usize).sum();
let s = (ones as f64 - total_bits as f64 / 2.0) / (total_bits as f64 / 2.0).sqrt();
// 使用误差函数计算 p-value
let p_value = erf_complement(s / 2.0_f64.sqrt());
p_value
}
fn erf_complement(x: f64) -> f64 {
// 简化的互补误差函数近似
let t = 1.0 / (1.0 + 0.3275911 * x.abs());
let poly = t * (0.254829592 + t * (-0.284496736 + t * (1.421413741
+ t * (-1.453152027 + t * 1.061405429))));
let result = 1.0 - poly * (-x * x).exp();
if x >= 0.0 { result } else { 2.0 - result }
}
fn main() {
use rand::RngExt;
let mut rng = rand::rng();
let mut bits = [0u8; 256];
rng.fill(&mut bits);
let p = frequency_test(&bits);
println!("Frequency test p-value: {}", p);
if p > 0.01 {
println!("PASS: 序列通过了频率测试");
} else {
println!("FAIL: 序列未通过频率测试");
}
}
随机数用途
随机数模拟现实中的场景,比如抽奖、掷骰子、游戏中随机关卡、电影特效等。
非密码学用途
use rand::RngExt;
use rand::seq::SliceRandom;
use rand::prelude::IndexedRandom;
fn main() {
let mut rng = rand::rng();
// 1. 模拟掷骰子
let dice_roll = rng.random_range(1..=6);
println!("骰子点数: {}", dice_roll);
// 2. 抽奖系统
let participants = ["Alice", "Bob", "Charlie", "David", "Eve"];
let winner = participants.choose(&mut rng).unwrap();
println!("中奖者: {}", winner);
// 3. 生成随机密码(非安全用途)
let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
.chars().collect();
let password: String = (0..12).map(|_| chars.choose(&mut rng).unwrap()).collect();
println!("随机密码: {}", password);
// 4. 蒙特卡洛方法估算 π
let n = 1_000_000;
let inside: usize = (0..n)
.filter(|_| {
let x: f64 = rng.random();
let y: f64 = rng.random();
x * x + y * y <= 1.0
})
.count();
let pi = 4.0 * inside as f64 / n as f64;
println!("π 的估算值: {} (误差: {:.6})", pi, (pi - std::f64::consts::PI).abs());
}
密码学用途
use rand::RngExt;
fn main() {
let mut rng = rand::rng();
// 1. 生成会话密钥
let mut session_key = [0u8; 32];
rng.fill(&mut session_key);
println!("会话密钥: {:02x?}", &session_key[..8]);
// 2. 生成 TLS 随机数(模拟)
let mut client_random = [0u8; 32];
rng.fill(&mut client_random);
println!("Client Random: {:02x?}", &client_random[..8]);
// 3. 生成密码学安全的随机密码
use rand::distr::{Alphanumeric, Distribution};
let password: String = (0..24)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
println!("安全随机密码: {}", password);
}
总结
本章全面介绍了随机数的理论基础和 Rust 实践。下表对核心知识点进行总结:
| 主题 | 要点 |
|---|---|
| 随机数标准 | 统计学伪随机性、密码学安全伪随机性、真随机性 |
| PRNG 算法 | LCG(简单但弱)、MT(通用)、PCG/Xoshiro(现代高性能) |
| TRNG 来源 | 硬件 RDRAND、操作系统熵源、物理现象(热噪声等) |
| rand crate | rand::random()、rand::rng()、ThreadRng、各种分布 |
| 分布类型 | Uniform、Normal、Bernoulli、Exp、LogNormal、Pareto |
| 集合操作 | choose()、choose_multiple()、shuffle()、partial_shuffle() |
| 密码学安全 | OsRng、CryptoRng trait、getrandom crate |
| 密码学用途 | nonce、salt、IV、密钥生成,必须使用 CSPRNG |
| 随机数测试 | NIST SP 800-22(16 项测试)、Dieharder、TestU01 |
| 安全原则 | 密钥生成必须用 CSPRNG、nonce 不可重用、种子需真随机 |
练习题
练习 1:实现简单的 LCG 并验证周期
实现一个 LCG 生成器,使用参数 $a = 1664525$、$c = 1013904223$、$m = 2^{32}$(Numerical Recipes 推荐参数),验证从种子 1 开始,序列的周期是否为 $2^{32}$。
// 提示:使用 HashSet 检测重复值
use std::collections::HashSet;
struct Lcg {
state: u32,
}
impl Lcg {
fn new(seed: u32) -> Self {
Self { state: seed }
}
fn next(&mut self) -> u32 {
self.state = self.state.wrapping_mul(1664525).wrapping_add(1013904223);
self.state
}
}
fn main() {
let mut lcg = Lcg::new(1);
let mut seen = HashSet::new();
let mut count = 0u64;
loop {
let val = lcg.next();
if !seen.insert(val) {
println!("周期长度: {}", count);
break;
}
count += 1;
}
}
练习 2:蒙特卡洛积分
使用蒙特卡洛方法计算以下定积分的近似值:
$$\int_0^1 \sin(x) , dx = 1 - \cos(1) \approx 0.4597$$
use rand::RngExt;
fn main() {
let mut rng = rand::rng();
let n = 1_000_000;
let mut sum = 0.0;
for _ in 0..n {
let x: f64 = rng.random(); // [0, 1)
sum += x.sin();
}
let result = sum / n as f64;
let exact = 1.0 - 1.0_f64.cos();
println!("蒙特卡洛结果: {:.6}", result);
println!("精确值: {:.6}", exact);
println!("误差: {:.6}", (result - exact).abs());
}
练习 3:密码学安全随机密码生成器
编写一个函数,生成包含大写字母、小写字母、数字和特殊字符的密码,确保每种字符至少出现一次,且使用密码学安全的随机源。
use rand::seq::SliceRandom;
use rand::RngExt;
use rand::prelude::IndexedRandom;
fn generate_secure_password(length: usize) -> String {
let mut rng = rand::rng();
let uppercase: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
let lowercase: Vec<char> = "abcdefghijklmnopqrstuvwxyz".chars().collect();
let digits: Vec<char> = "0123456789".chars().collect();
let special: Vec<char> = "!@#$%^&*()_+-=[]{}|;:,.<>?".chars().collect();
let mut password: Vec<char> = Vec::with_capacity(length);
// 确保每类字符至少一个
password.push(*uppercase.choose(&mut rng).unwrap());
password.push(*lowercase.choose(&mut rng).unwrap());
password.push(*digits.choose(&mut rng).unwrap());
password.push(*special.choose(&mut rng).unwrap());
// 填充剩余字符
let all_chars: Vec<char> = uppercase.iter()
.chain(lowercase.iter())
.chain(digits.iter())
.chain(special.iter())
.copied()
.collect();
while password.len() < length {
password.push(*all_chars.choose(&mut rng).unwrap());
}
// 洗牌打乱顺序
password.shuffle(&mut rng);
password.into_iter().collect()
}
fn main() {
let password = generate_secure_password(20);
println!("安全密码: {}", password);
println!("长度: {}", password.len());
}
练习 4:随机数质量可视化检验
编写程序生成 10,000 个随机点 $(x, y)$,其中 $x, y \in [0, 1)$,将结果输出为 CSV 格式,然后用散点图可视化检验分布的均匀性。
use rand::RngExt;
use std::io::Write;
fn main() {
let mut rng = rand::rng();
let mut file = std::fs::File::create("random_points.csv")
.expect("无法创建文件");
writeln!(file, "x,y").unwrap();
for _ in 0..10_000 {
let x: f64 = rng.random();
let y: f64 = rng.random();
writeln!(file, "{:.6},{:.6}", x, y).unwrap();
}
println!("已生成 random_points.csv,包含 10000 个随机点");
println!("可使用 Python matplotlib 或其他工具绘制散点图验证均匀性");
}