密码学安全随机数生成:CSPRNG 原理与熵源管理

密码学概念 · 2026-06-02

为什么随机数对密码学至关重要

密码学中几乎所有安全协议的正确性和安全性,都依赖于随机数的质量。密钥生成需要随机数,初始化向量(IV)需要随机数,Nonce 需要随机数,挑战-应答协议中的挑战值需要随机数,甚至零知识证明中的模拟器也需要随机数。如果随机数可被预测或重复,整个密码系统的安全性将轰然倒塌。

历史上因随机数质量问题导致的安全事故屡见不鲜:

  • 2012 年 Android 比特币钱包漏洞:Java SecureRandom 实现中熵源不足,导致生成的 ECDSA 私钥可被恢复,大量比特币被盗。
  • 2010 年 Sony PS3 ECDSA 密钥泄露:Sony 在实现 ECDSA 签名时使用了固定的 k 值(而非随机数),导致私钥被直接计算出来。
  • Debian OpenSSL 漏洞(2008):一个错误的代码注释移除了熵源中的大部分随机性,导致生成的密钥空间从 $2^{128}$ 骤降至约 $2^{15}$,所有使用该版本 OpenSSL 生成的密钥均不安全。
这些案例说明:随机数生成不是工程细节,而是安全基础

随机数的分类与定义

真随机数生成器(TRNG)

真随机数生成器(True Random Number Generator)从物理现象中提取随机性,其输出在理论上不可预测。常见的物理熵源包括:

熵源类型物理原理典型速率
热噪声(Johnson-Nyquist 噪声)电阻中电子的热运动~Mbps
振荡器抖动(Jitter)两个自由振荡器的相位差~Mbps
半导体击穿噪声齐纳二极管的雪崩击穿~Mbps
鼠标/键盘时序用户输入的微观时序差异~bps
磁盘寻道时间机械硬盘的旋转/寻道抖动~kbps
大气噪声无线电频段的电磁噪声~kbps
TRNG 的输出通常存在偏差(bias)和相关性,因此需要经过健康测试(health test)和熵提取(entropy extraction)后才能使用。

伪随机数生成器(PRNG)

伪随机数生成器(Pseudorandom Number Generator)使用确定性算法,从一个较短的种子(seed)扩展为较长的输出序列。其安全性完全依赖于种子的保密性和随机性。

PRNG 的核心安全性质是不可区分性(indistinguishability):对于任何多项式时间的敌手,PRNG 的输出与真正的均匀随机串在计算上不可区分。

密码学安全伪随机数生成器(CSPRNG)

CSPRNG 是满足密码学安全要求的 PRNG,其形式化定义如下:

定义(CSPRNG):设 $G: \{0,1\}^n \rightarrow \{0,1\}^{l(n)}$ 是一个确定性多项式时间算法,其中 $l(n) > n$。如果对于所有概率多项式时间区分器 $D$,有:
>
$$\left| \Pr[D(G(U_n)) = 1] - \Pr[D(U_{l(n)}) = 1] \right| \leq \text{negl}(n)$$
>
其中 $U_n$ 表示 $n$ 位均匀随机串,$\text{negl}(n)$ 是可忽略函数,则称 $G$ 是一个密码学安全的伪随机数生成器。
与普通 PRNG 不同,CSPRNG 还需要满足两个额外的安全性质:

后向安全性(Backward Security / Break-in Recovery):即使攻击者在某一时刻获取了 CSPRNG 的内部状态,也无法恢复之前输出的随机数。

前向安全性(Forward Security):即使攻击者获取了当前内部状态,在状态更新后,之前的状态信息不会帮助预测未来的输出。

CSPRNG 的安全模型

不可预测性(Unpredictability)

CSPRNG 的核心安全要求是下一比特不可预测性(next-bit unpredictability):给定前 $k$ 个输出比特,任何多项式时间算法无法以显著优于 $1/2$ 的概率预测第 $k+1$ 个比特。

Yao 在 1982 年证明了以下等价性定理:

Yao 定理:一个 PRNG 满足下一比特不可预测性,当且仅当它是计算上不可区分的(即满足 CSPRNG 的定义)。

状态泄露扩展(State Compromise Extension)

在实际系统中,CSPRNG 的内部状态可能因侧信道攻击、内存泄露等原因被部分或全部泄露。因此,现代 CSPRNG 设计需要考虑状态泄露后的恢复能力:

  • 完全恢复型:在获取新的熵输入后,CSPRNG 的安全性自动恢复。
  • 有限恢复型:只能保证未来输出的安全性,无法恢复过去输出的安全性。
  • 无恢复型:一旦状态泄露,所有后续输出均不可信。
NIST SP 800-90A 标准要求 CSPRNG 至少实现完全恢复型安全。

主流 CSPRNG 算法架构

基于分组密码的 CSPRNG

利用 AES 等分组密码算法构造 CSPRNG 是最经典的方法之一。

CTR_DRBG(Counter Mode DRBG):NIST SP 800-90A 推荐的方案之一,使用 AES-CTR 模式生成伪随机比特流。

工作流程:

  • 初始化:使用种子密钥 $K$ 和计数器 $V$
  • 生成:对 $V+1, V+2, \ldots$ 进行 AES 加密,输出密文块
  • 更新:生成一定量输出后,用新的熵源更新 $(K, V)$
安全强度:AES-128 提供 128 位安全强度,AES-256 提供 256 位安全强度。

基于 SM4 的 CTR_DRBG:在国密体系中,可以使用 SM4 替代 AES 实现 CTR_DRBG,其原理和流程完全相同。

基于哈希函数的 CSPRNG

Hash_DRBG:NIST SP 800-90A 推荐的另一方案,基于哈希函数(如 SHA-256)构造。

工作流程:

  • 初始化:$V = \text{Hash}(\text{seed})$,$C = \text{Hash}(V \| 0x00)$
  • 生成:输出 $\text{Hash}(V)$,然后 $V = V + 1$
  • 更新:$V = \text{Hash}(V \| C \| \text{new\_entropy})$
基于 SM3 的 Hash_DRBG:在国密体系中,可以使用 SM3 替代 SHA-256 实现 Hash_DRBG。

基于数论的 CSPRNG

Blum Blum Shub(BBS):基于二次剩余问题的困难性。

$$x_{i+1} = x_i^2 \mod N$$

其中 $N = pq$ 是两个大素数的乘积。输出取 $x_i$ 的最低有效位。BBS 的安全性可归约到整数分解问题的困难性,但速度较慢,主要用于理论分析。

ANSI X9.31 PRNG:基于 3DES 或 AES,使用当前时间值和内部状态生成随机数。曾广泛用于金融行业,但因潜在弱点已被 NIST SP 800-90A 方案取代。

Linux 内核 CSPRNG

Linux 内核的随机数子系统是 CSPRNG 工程实现的典范,其架构经历了多次演进:

早期设计(< 3.17)

  • /dev/random:阻塞式,估计熵池中的熵量,熵不足时阻塞
  • /dev/urandom:非阻塞式,熵不足时仍输出(依赖 CSPRNG 的安全性)
当前设计(≥ 3.17,特别是 4.8+)
  • 使用 ChaCha20 算法作为 CSPRNG 的核心
  • 引入 CRNG(ChaCha20 Random Number Generator)
  • getrandom() 系统调用:在熵池初始化完成前阻塞,初始化后行为与 /dev/urandom 相同
  • /dev/random/dev/urandom 在熵池初始化后行为一致
熵池结构
  • 输入池(Input Pool):收集来自硬件、设备驱动、中断时序等的熵
  • 阻塞池(Blocking Pool):用于 /dev/random 的阻塞语义
  • CRNG:基于 ChaCha20 的 CSPRNG,从阻塞池播种

熵源管理

熵的度量

信息论中,熵(Entropy)度量随机变量的不确定性。对于离散随机变量 $X$,Shannon 熵定义为:

$$H(X) = -\sum_{x \in \mathcal{X}} P(X = x) \log_2 P(X = x)$$

密码学中更常用的是最小熵(Min-Entropy):

$$H_\infty(X) = -\log_2 \left( \max_{x \in \mathcal{X}} P(X = x) \right)$$

最小熵给出了最坏情况下的不确定性度量,是密码学安全分析的标准工具。

熵源的健康测试

NIST SP 800-90B 规定了熵源的健康测试要求:

重复计数测试(Repetition Count Test):检测熵源是否陷入固定值输出。如果在连续 $N$ 个样本中出现相同的值,则报警。

自适应比例测试(Adaptive Proportion Test):检测熵源中某个值的出现频率是否异常偏高。

熵池管理策略

Linux 内核的熵估计

  • 每个熵源贡献的熵量由驱动程序声明
  • 内核维护一个熵计数器(entropy count)
  • 当熵计数器达到阈值(通常为 256 位)时,认为熵池已充分播种
熵混合(Entropy Mixing)
  • 使用 XOR 操作将新熵混合到熵池中
  • 使用 LFSR(线性反馈移位寄存器)进行扩散
  • 现代设计使用密码学哈希函数进行混合

NIST SP 800-90 系列标准

NIST(美国国家标准与技术研究院)的 SP 800-90 系列标准是 CSPRNG 领域最权威的标准体系:

标准内容状态
SP 800-90A随机数生成机制(DRBG)2015 年修订版
SP 800-90B熵源规范2018 年版
SP 800-90CRNG 整体架构设计草案阶段
SP 800-90A 定义的 DRBG 机制
  • Hash_DRBG:基于 SHA-2 系列哈希函数
  • HMAC_DRBG:基于 HMAC 构造
  • CTR_DRBG:基于 AES 分组密码
国密对应标准
  • GM/T 0005-2012《随机性检测规范》
  • GM/T 0006-2012《密码应用标识规范》
  • 在国密体系中,CTR_DRBG 和 Hash_DRBG 可以使用 SM4 和 SM3 替代 AES 和 SHA-256

低熵环境下的挑战

嵌入式设备

嵌入式设备(如 IoT 传感器、智能卡)通常缺乏丰富的物理熵源:

  • 没有键盘/鼠标等用户交互设备
  • 没有机械硬盘
  • 启动时系统状态高度可预测
工程应对策略
  • 在制造阶段注入唯一性密钥(Unique ID)作为初始种子
  • 利用 SRAM 上电时的随机初始状态
  • 采集 ADC(模数转换器)的最低位噪声
  • 使用专用硬件 TRNG 模块

虚拟机

虚拟机面临更严重的熵不足问题:

  • 虚拟硬件的确定性行为减少了可用熵源
  • 虚拟机克隆导致初始状态完全相同
  • 快照恢复后熵池状态回退
工程应对策略
  • 使用 virtio-rng 设备从宿主机传递熵
  • 利用 CPU 的 RDRAND/RDSEED 指令(如果虚拟化层透传)
  • 在云环境中使用云厂商提供的熵服务(如 AWS Nitro Enclaves 的熵源)
  • 使用 havegedjitterentropy 等软件熵增强工具

容器环境

Docker 等容器共享宿主机的熵池,通常不会出现熵不足问题,但需要注意:

  • 容器内的 /dev/random/dev/urandom 实际上是宿主机的设备
  • 大量容器同时启动时可能短暂消耗熵池
  • 建议使用 getrandom() 系统调用而非直接读取设备文件

CSPRNG 的安全使用原则

播种原则

  • 种子长度:种子应至少提供 128 位安全强度(即至少 128 位最小熵)
  • 种子唯一性:每次系统启动应使用不同的种子
  • 种子保密性:种子应存储在安全区域,防止泄露
  • 持续重播种:在获取新熵后应定期重播种

使用禁忌

  • 禁止自行设计 CSPRNG 算法:应使用经过标准认证的实现
  • 禁止使用非密码学 PRNG:如 rand()Math.random()Linear Congruential Generator
  • 禁止重复使用种子:每次生成密钥应使用新的随机种子
  • 禁止截断输出:CSPRNG 的输出应完整使用,不应截断后作为密钥

常见错误

C
// 错误:使用时间作为随机种子
srand(time(NULL));
int key = rand();  // 完全不安全!

// 正确:使用 CSPRNG
#include <openssl/rand.h>
unsigned char key[32];
RAND_bytes(key, sizeof(key));  // 使用 OpenSSL 的 CSPRNG

国密体系中的随机数生成

在国密(GM)体系中,随机数生成需要满足以下要求:

  • GM/T 0005-2012:规定了随机性检测的统计测试方法,包括频数测试、块内频数测试、游程测试、矩阵秩测试等 15 项测试
  • GM/T 0003.3-2012:SM2 算法标准第 3 部分(密钥交换协议)中规定了密钥生成时对随机数的要求
  • GM/T 0004-2012:SM3 密码杂凑算法标准
  • GM/T 0009-2012:SM2 密码算法使用规范中对随机数的要求
在国密 TLS(GM/T 0024-2014)中,ClientHello 和 ServerHello 的随机数必须使用符合 GM/T 0005-2012 的随机数生成器产生。

相关实践