随机数生成与熵源:安全随机数生成器、真随机与伪随机
说到密钥,第一个绕不开的就是随机数。你想想看,如果生成密钥的随机数不够「随机」,那这把锁说白了就是纸糊的。我刚入行那会儿,遇到过有人直接用 C 语言的 rand() 生成 RSA 密钥,结果呢?同一台机器每次启动后前几个密钥一模一样——因为种子是固定的。嗯,这个坑我后面会细讲。
今天咱们就把这块掰开揉碎了聊。先搞清楚什么是真随机,什么是伪随机,再来看安全随机数生成器(CSPRNG)到底怎么用。
真随机 vs 伪随机:本质区别
很多人觉得「随机就是随便来个数」,其实差远了。真随机数来源于物理世界的不可预测事件,比如热噪声、放射性衰变、鼠标移动轨迹。伪随机数则是通过算法从种子值计算出来的序列,只要种子一样,序列就完全一样。
| 对比项 | 真随机数 (TRNG) | 伪随机数 (PRNG) |
|---|---|---|
| 来源 | 物理熵源 | 算法 + 种子 |
| 可预测性 | 理论上不可预测 | 已知种子可完全预测 |
| 速度 | 慢(通常几十 KB/s) | 极快(GB/s 级别) |
| 用途 | 种子、密钥生成、一次性随机 | 模拟、游戏、非安全场景 |
| 安全要求 | 必须用于密钥 | 仅用于非安全场景 |
其实真随机和伪随机不是非此即彼的关系。大多数安全系统用的是混合方式:先用真随机数作为种子,再用伪随机算法快速扩展出大量随机数。只要种子足够熵,结果就是安全的。
安全随机数生成器(CSPRNG)
CSPRNG 全称是 Cryptographically Secure Pseudo-Random Number Generator。它跟普通 PRNG 最大的区别在于——它必须扛住「即使攻击者知道算法,不知道种子,就无法预测任何输出」。业内常用的 CSPRNG 有:
- /dev/urandom(Linux/BSD)—— 一旦初始化,永不阻塞,熵池不够时会退化为伪随机
- getrandom()(Linux 3.17+)—— 比 /dev/urandom 更可控,可指定是否阻塞等待熵
- CryptGenRandom(Windows)—— 微软的 CSPRNG,但早期实现有过 bug
- 操作系统提供的 API(如
arc4random、SecureRandom)
getrandom() 且不设 GRND_NONBLOCK,让它启动时等等熵。虽然第一次调用可能卡个几毫秒,但种子质量有保障。
熵源与熵估计
熵源是随机数的「原材料」。常见的熵源有:
- 硬件噪声:CPU 内部的热噪声、时钟抖动、磁盘寻道时间波动
- 用户交互:鼠标移动、键盘敲击间隔(服务器上很难获取)
- 网络包到达间隔:不依赖硬件,但容易被攻击者观察
- 专用硬件:TPM 芯片、Intel RDRAND 指令、熵源芯片
熵估计是个玄学。你没法精确知道一个事件里到底有多少「不可预测性」。内核通常用保守估计,比如每次键盘中断算半个比特熵。我在项目中见过一个嵌入式设备,因为熵池长期处于「欠熵」状态,导致 openssl 生成密钥时卡死 10 多秒——嗯,后来加了硬件熵源才解决。
实践:如何获取高质量随机数
写代码的时候别自己实现随机数生成器。用现成的、经过审计的 API:
# Python 3.6+ 标准做法
import secrets
# 生成 32 字节真随机数(需要足够熵)
key = secrets._bytes(32)
# 生成十六进制密钥字符串
secret_key = secrets._hex(16)
print(secret_key) # 每次都不同
random.randint 生成 AES 密钥,结果被逆向出种子后,所有加密数据都变成了裸奔。 random 的算法是 Mersenne Twister,专门为模拟设计,不是 CSPRNG。
如果是在 Linux 上直接用 C 写底层代码,推荐用 getrandom 系统调用:
#include <sys/random.h>
#include <stdio.h>
unsigned char buf[32];
if (getrandom(buf, sizeof(buf), 0) == sizeof(buf)) {
// buf 里是安全的随机数
printf("随机数生成成功\n");
}
一次我个人踩过的坑
几年前我维护一个 IoT 网关,由于硬件没有 TRNG,就用 /dev/urandom 生成密钥。一切正常。直到有一次系统刚启动,内核熵池还没积累够,就调用了 /dev/urandom——你猜怎么着?生成的 SSH 主机密钥跟另一台设备一模一样!因为两个设备的启动序列、时间戳、硬件都一样,初始熵池几乎为零,/dev/urandom 退化成确定性输出。从那以后,我对「熵初始化」特别敏感。哪怕多等 100ms,也要确保种子里面混进了真正的物理噪声。
知识体系:随机数与密钥边界
总结一句话:真随机负责「播种」,CSPRNG 负责「产出」。安全底线是永远不要用普通 PRNG 生成密钥、随机数种子、IV 或随机盐值。如果平台支持,直接用操作系统提供的安全随机接口。