随机数

概述

随机数在计算机科学和编程中扮演着重要角色,广泛应用于密码学、模拟、游戏、统计采样等领域。

随机数的类型

1. 伪随机数(Pseudo-Random Numbers)

  • 由确定性算法生成
  • 给定相同的种子,会产生相同的序列
  • 周期性重复
  • 速度快,但可预测

2. 真随机数(True Random Numbers)

  • 基于物理现象(如热噪声、放射性衰变)
  • 不可预测
  • 无周期性
  • 生成速度较慢

常见随机数生成算法

线性同余生成器(LCG)

X(n+1) = (a * X(n) + c) mod m
  • 参数:乘数 a、增量 c、模数 m
  • 优点:简单、快速
  • 缺点:周期有限、统计特性一般

梅森旋转算法(Mersenne Twister)

  • 周期:2^19937 - 1
  • 优点:周期长、分布均匀
  • 缺点:状态空间大(2.5KB)
  • 应用:Python 的 random 模块默认算法

xorshift 系列

  • 基于位移和异或操作
  • 优点:速度快、状态空间小
  • 缺点:某些变体统计特性不佳

加密安全随机数生成器(CSPRNG)

  • 如:/dev/urandom、CryptGenRandom
  • 用于密码学场景
  • 不可预测性至关重要

编程语言中的随机数

Python

import random
 
# 伪随机数
random.random()          # [0.0, 1.0) 浮点数
random.randint(1, 10)    # [1, 10] 整数
random.choice(['a', 'b', 'c'])  # 随机选择
 
# 加密安全随机数
import secrets
secrets.token_hex(16)    # 随机十六进制字符串
secrets.randbelow(100)   # [0, 100) 安全随机整数

JavaScript

// 伪随机数
Math.random()            // [0, 1) 浮点数
 
// 加密安全随机数
crypto.getRandomValues(new Uint32Array(1))

Java

import java.util.Random;
import java.security.SecureRandom;
 
// 伪随机数
Random rand = new Random();
rand.nextInt(10);        // [0, 10) 整数
 
// 加密安全随机数
SecureRandom secureRand = new SecureRandom();
secureRand.nextInt(100);

随机数种子

种子(Seed)用于初始化随机数生成器的状态:

import random
 
random.seed(42)          # 设置种子
print(random.random())   # 每次运行结果相同

用途:

  • 可重现的实验结果
  • 调试和测试
  • 确定性模拟

应用场景

场景推荐类型示例
游戏伪随机随机地图生成、掉落概率
模拟伪随机蒙特卡洛模拟
密码学真随机/CSPRNG密钥生成、会话令牌
抽奖真随机公平性要求高
测试伪随机(固定种子)可重现的测试用例

注意事项

  1. 安全性:密码学应用必须使用加密安全的随机数
  2. 种子选择:避免使用可预测的种子(如时间戳)
  3. 周期性:伪随机数有周期,大量使用时需注意
  4. 分布:了解生成器的分布特性

Linux 系统生成随机数

Linux 内核提供了两种随机数生成机制,通过特殊的字符设备文件供用户空间访问。

/dev/random 和 /dev/urandom

特性/dev/random/dev/urandom
阻塞行为熵池不足时阻塞不阻塞
安全性理论上更高足够安全
性能较慢较快
适用场景密钥生成(极少量)一般加密用途
推荐程度不推荐推荐使用

注意:现代 Linux(内核 4.8+)中,两者安全性差异已很小。大多数情况下推荐使用 /dev/urandom

从命令行获取随机数

# 读取随机字节(十六进制)
xxd -l 16 -p /dev/urandom
 
# 生成随机密码
openssl rand -base64 12
 
# 使用 head 读取
head -c 16 /dev/urandom | xxd -p
 
# 生成随机数字(0-99)
od -An -N2 -tu2 /dev/urandom | awk '{print $1 % 100}'
 
# 使用 shuf 生成随机数
shuf -i 1-100 -n 1
 
# 生成 UUID
cat /proc/sys/kernel/random/uuid

Shell 脚本中使用随机数

#!/bin/bash
 
# 使用 $RANDOM(0-32767)
echo $RANDOM
 
# 生成指定范围的随机数(1-100)
echo $((RANDOM % 100 + 1))
 
# 从 /dev/urandom 读取
random_num=$(od -An -N2 -tu2 /dev/urandom | tr -d ' ')
echo $random_num
 
# 生成随机字符串
random_str=$(head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 16)
echo $random_str

系统调用 getrandom()

Linux 3.17+ 提供了 getrandom() 系统调用:

#include <linux/random.h>
#include <sys/syscall.h>
#include <unistd.h>
 
int main() {
    unsigned char buf[16];
    
    // 使用 getrandom 系统调用
    ssize_t ret = syscall(SYS_getrandom, buf, sizeof(buf), 0);
    
    // 或使用 glibc 封装(glibc 2.25+)
    // ssize_t ret = getrandom(buf, sizeof(buf), 0);
    
    return 0;
}

getrandom() 标志:

  • 0:默认行为,熵池未初始化时阻塞
  • GRND_NONBLOCK:非阻塞模式,熵池未初始化时返回 -1
  • GRND_RANDOM:使用 /dev/random 的熵池
  • GRND_INSECURE(Linux 5.6+):获取未初始化的熵(仅用于引导早期)

/proc/sys/kernel/random/ 目录

查看随机数生成器状态:

# 可用熵位数
cat /proc/sys/kernel/random/entropy_avail
 
# 熵池容量
cat /proc/sys/kernel/random/poolsize
 
# 自上次启动以来的熵估计
cat /proc/sys/kernel/random/entropy_count
 
# 生成 UUID
cat /proc/sys/kernel/random/uuid
 
# 生成随机 UUID
cat /proc/sys/kernel/random/uuid

使用 OpenSSL 生成随机数

# 生成 16 字节随机数(十六进制)
openssl rand -hex 16
 
# 生成 16 字节随机数(Base64)
openssl rand -base64 16
 
# 生成随机密码(字母数字)
openssl rand -base64 12 | tr -d '/+=' | head -c 12
 
# 写入文件
openssl rand -out random.bin 32

熵源与 haveged

虚拟机或容器中可能熵不足,可使用 haveged 增加熵:

# 安装 haveged
sudo apt install haveged
 
# 查看熵可用性
cat /proc/sys/kernel/random/entropy_avail
 
# haveged 会持续监控并补充熵
systemctl status haveged

在 C 程序中读取 /dev/urandom

#include <stdio.h>
#include <stdlib.h>
 
int main() {
    FILE *fp = fopen("/dev/urandom", "rb");
    if (!fp) {
        perror("fopen");
        return 1;
    }
    
    unsigned char buf[16];
    size_t n = fread(buf, 1, sizeof(buf), fp);
    fclose(fp);
    
    // 打印十六进制
    for (int i = 0; i < n; i++) {
        printf("%02x", buf[i]);
    }
    printf("\n");
    
    return 0;
}

Linux 随机数架构图

┌─────────────────────────────────────────────────────┐
│                    硒源(Entropy Sources)            │
├─────────────────────────────────────────────────────┤
│  键盘中断  │  鼠标中断  │  磁盘IO  │  硬件RNG  │ ...  │
└─────┬─────┴─────┬─────┴────┬────┴─────┬─────┴──────┘
      │           │          │          │
      ▼           ▼          ▼          ▼
┌─────────────────────────────────────────────────────┐
│              Linux 内核熵池(Entropy Pool)           │
│                   (SHA-1 混合)                       │
└─────────────────────┬───────────────────────────────┘
                      │
          ┌───────────┴───────────┐
          ▼                       ▼
┌─────────────────┐     ┌─────────────────┐
│   /dev/random   │     │  /dev/urandom   │
│   (阻塞式)      │     │   (非阻塞)     │
└─────────────────┘     └─────────────────┘
          │                       │
          ▼                       ▼
┌─────────────────────────────────────────────────────┐
│           用户空间应用程序(通过 read/getrandom)      │
└─────────────────────────────────────────────────────┘

最佳实践

  1. 优先使用 /dev/urandomgetrandom()
  2. 避免自己实现随机数生成算法
  3. 对于加密密钥生成,使用专门的库(如 OpenSSL)
  4. 注意虚拟化环境中的熵不足问题
  5. 生产环境考虑部署 haveged 或 rngd 服务

参考资源