Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第二十七 随机数

随机数

随机数概述

随机性是计算机科学中一个基础而深刻的概念。从蒙特卡洛模拟到密码学协议,从游戏引擎到机器学习,随机数无处不在。然而,计算机本质上是确定性的机器——给定相同的输入,它总是产生相同的输出。那么,计算机如何产生“随机“的数字呢?

这正是随机数生成理论要解决的核心问题。理解随机数的本质、分类和生成原理,对于编写安全、可靠的软件至关重要。一个看似微不足道的随机数生成器缺陷,可能导致加密系统被完全攻破,或导致模拟实验得出错误的结论。

随机数的三个标准

根据密码学原理,随机数的随机性检验可以分为三个标准: [1]

  1. 统计学伪随机性。统计学伪随机性指的是在给定的随机比特流样本中,1的数量大致等于0的数量,同理,“10”“01”“00”“11“四者数量大致相等。类似的标准被称为统计学随机性。满足这类要求的数字在人类“一眼看上去“是随机的。更严格地说,统计学伪随机性要求比特流能够通过一系列统计检验,包括频率检验、游程检验、频谱检验等。数学上,一个理想的随机比特流中每个比特位为1的概率为 $p = 0.5$,且各比特位之间相互独立。

  2. 密码学安全伪随机性。其定义为,给定随机样本的一部分和随机算法,不能有效的演算出随机样本的剩余部分。这意味着即使攻击者观察到了生成器输出的任意长前缀,也无法以显著优于随机猜测的概率预测下一个输出比特。形式化表述为:对于任意多项式时间算法 $A$,预测下一个比特的成功概率与 $1/2$ 的差值可忽略不计:

$$|\Pr[A(x_1, x_2, \ldots, x_n) = x_{n+1}] - \frac{1}{2}| \leq \text{negl}(n)$$

  1. 真随机性。其定义为随机样本不可重现。实际上只要给定边界条件,真随机数并不存在,可是如果产生一个真随机数样本的边界条件十分复杂且难以捕捉(比如计算机当地的本底辐射波动值),可以认为用这个方法演算出来了真随机数。真随机数的核心特征是不可预测性和不可重现性——即使完全了解生成机制,也无法在事先预测输出结果。

随机数分类

相应的,随机数也分为三类:

分类满足的标准生成方式典型用途
伪随机数(PRNG)统计学伪随机性确定性算法 + 种子模拟、游戏、测试
密码学安全伪随机数(CSPRNG)统计学 + 密码学安全伪随机性密码学算法 + 熵源密钥生成、nonce、salt
真随机数(TRNG)全部三个标准物理现象高安全场景、种子生成
  1. 伪随机数:满足第一个条件的随机数。由确定性算法生成,给定相同的种子(seed),将产生完全相同的序列。虽然不具备密码学安全性,但生成速度快、可重现,适用于不需要安全保证的场景。
  2. 密码学安全的伪随机数:同时满足前两个条件的随机数。可以通过密码学安全伪随机数生成器计算得出。即使攻击者获取了部分输出,也无法推断出之前或之后的输出。
  3. 真随机数:同时满足三个条件的随机数。来源于物理随机过程,不可预测也不可重现。

随机数在密码学中非常重要,保密通信中大量运用的会话密钥的生成即需要真随机数的参与。如果一个随机数生成算法是有缺陷的,那么会话密钥可以直接被推算出来。若果真发生这种事故,那么任何加密算法都失去了意义。

随机数分为伪随机数和真随机数。伪随机数又分为弱伪随机数和强伪随机数。

随机数生成器

随机数生成器有两种类型:真正的随机数生成器和伪随机数生成器。

随机数生成器原理

伪随机数生成器原理

伪随机数生成器(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 定理:

  1. $c$ 与 $m$ 互质($\gcd(c, m) = 1$)
  2. $a - 1$ 能被 $m$ 的所有质因子整除
  3. 若 $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 等价
WindowsCryptGenRandom / BCryptGenRandomWindows CryptoAPI 提供的 CSPRNG
WindowsProcessPrngWindows 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_corerand 生态的基础抽象层,定义了核心 trait:

  • RngExt:用户层 trait,提供 random()random_range()fill()sample() 等便捷方法
  • SeedableRng:可从种子初始化的生成器 trait
  • CryptoRng:标记 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 在不同平台上自动选择最佳熵源:

平台熵源
Linuxgetrandom() 系统调用
macOSgetentropy()
WindowsBCryptGenRandom
WebAssemblyCrypto.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

密钥生成的安全要求

密钥生成对随机数的质量有严格要求:

  1. 最小熵要求:128 位密钥至少需要 128 位熵。使用弱随机数生成器会导致密钥空间大幅缩小,使暴力破解成为可能。
  2. 不可预测性:密钥必须使用 CSPRNG 生成,绝不能使用普通 PRNG(如 LCG、Mersenne Twister)。
  3. 种子安全:CSPRNG 的种子本身必须来自真随机源(操作系统熵池或硬件 TRNG)。
  4. 避免重用: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)

随机数测试

如何验证一个随机数生成器是否“足够随机“?这需要借助统计测试套件。

统计测试套件

常见的随机数测试套件包括:

测试套件开发者测试数量说明
DiehardMarsaglia15经典测试集,已过时
DieharderBrown100+Diehard 的扩展版
TestU01L’Ecuyer10+包含 Big Crush、Small Crush
NIST SP 800-22NIST16密码学标准测试
PractRandSibidanov持续运行最严格的测试之一

NIST SP 800-22 测试

NIST SP 800-22 是美国国家标准与技术研究院发布的随机数测试标准,包含 16 项测试:

  1. 频率(Frequency)测试:检验整个比特流中 0 和 1 的比例是否接近 0.5
  2. 块内频率(Block Frequency)测试:在 M 位块内检验频率
  3. 游程(Runs)测试:检验连续相同比特(游程)的数量分布
  4. 最长游程(Longest Run)测试:在一个块内检验最长游程
  5. 二元矩阵秩(Binary Matrix Rank)测试:检验固定大小矩阵的秩
  6. 离散傅里叶变换(FFT)测试:检测周期性模式
  7. 非重叠模板匹配(Non-overlapping Template)测试:检测特定比特模式的出现频率
  8. 重叠模板匹配(Overlapping Template)测试:类似但模板可重叠
  9. 通用统计(Universal Statistical)测试:基于 Maurer 的通用统计
  10. Lempel-Ziv 压缩(Linear Complexity)测试:检验线性复杂度
  11. 序列(Serial)测试:检验 $m$-bit 模式的频率
  12. 近似熵(Approximate Entropy)测试:评估序列的不可预测性
  13. 累积和(Cumulative Sums)测试:检测部分序列中 0 和 1 的偏向
  14. 随机偏移(Random Excursions)测试:检验随机游走特性
  15. 随机偏移变体(Random Excursions Variant)测试:随机游走的变体
  16. 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 craterand::random()rand::rng()、ThreadRng、各种分布
分布类型Uniform、Normal、Bernoulli、Exp、LogNormal、Pareto
集合操作choose()choose_multiple()shuffle()partial_shuffle()
密码学安全OsRngCryptoRng trait、getrandom crate
密码学用途nonce、salt、IV、密钥生成,必须使用 CSPRNG
随机数测试NIST SP 800-22(16 项测试)、Dieharder、TestU01
安全原则密钥生成必须用 CSPRNG、nonce 不可重用、种子需真随机

《The Rust Rand Book》

练习题

练习 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 或其他工具绘制散点图验证均匀性");
}