伪随机数与随机性测试

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

密码学中几乎所有操作都依赖随机性:

  • 生成密钥
  • 生成初始化向量(IV)
  • 生成盐值(Salt)
  • 生成临时会话密钥
  • 填充数据

如果随机数可预测或重复,整个密码系统就可能被攻破。历史上多次重大安全漏洞都源于随机数生成器的弱点。

真随机与伪随机

真随机数生成器(TRNG)

从物理过程中获取随机性:

  • 热噪声、放射性衰变
  • 鼠标移动、键盘按键时间间隔
  • 磁盘读写时间、网络包到达时间
  • 现代 CPU 提供的硬件随机源(如 Intel RDRAND)

优点:不可预测;缺点:速度慢,可能不够均匀。

伪随机数生成器(PRNG)

从一个种子(Seed)出发,通过确定性算法生成看起来随机的数列。相同种子产生相同序列。

典型的非密码学 PRNG:

  • 线性同余生成器(LCG):xn+1 = (a × xn + c) mod m。速度快但可预测,不适合密码学。
  • Mersenne Twister:周期极长(219937-1),统计性质好,但可预测,不适合密码学。

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

CSPRNG 是满足以下额外安全性质的 PRNG:

  1. 下一比特不可预测性:给定输出序列的前 k 个比特,不存在多项式时间算法能以显著大于 1/2 的概率预测第 k+1 个比特。
  2. 回溯不可行性:即使状态后来被泄露,之前生成的输出仍然不可预测(称为前向安全性)。

常见 CSPRNG

  • /dev/urandom(Linux)和 CryptGenRandom(Windows):操作系统提供的 CSPRNG,从硬件和系统事件中收集熵。
  • Fortuna:由 Bruce Schneier 等人设计,自动管理熵池,被用于 Windows CNG。
  • HMAC-DRBG / Hash-DRBG:NIST SP 800-90A 标准定义的基于哈希的 DRBG。
  • ChaCha20-based CSPRNG:Linux 4.8+ 默认的 /dev/urandom 实现。

熵与熵池

操作系统的 CSPRNG 从各种来源收集熵(不可预测的信息),存入熵池:

  • 启动时从硬件收集初始熵
  • 运行中持续从设备驱动、中断时间等收集熵
  • 用熵池的状态作为 CSPRNG 的种子

问题:嵌入式设备和虚拟机可能缺乏足够的熵源,导致启动时生成的随机数质量差(如早期的 Android 比特币钱包漏洞)。

随机性统计测试

评估一个序列是否"看起来随机"的标准方法:

NIST SP 800-22 测试套件

NIST 提供的 15 项统计测试:

  • 频率测试(Monobit Test):0 和 1 的数量是否大致相等
  • 块内频率测试:每个块内 0 和 1 是否大致相等
  • 游程测试(Runs Test):连续相同比特的序列长度分布
  • 最长游程测试:最长 1 游程的长度
  • 二元矩阵秩测试:矩阵的线性独立性
  • 离散傅里叶变换测试:频域中的周期性特征
  • 非重叠模板匹配测试:特定模式出现频率
  • 重叠模板匹配测试
  • 通用统计测试(Maurer's Test):可压缩性
  • 线性复杂度测试
  • 串行测试:相邻比特组合的均匀性
  • 近似熵测试
  • 累积和测试
  • 随机游走测试
  • ...等

实战建议

  • 永远不要自己实现 PRNG:使用操作系统提供的 CSPRNG(/dev/urandom、java.security.SecureRandom 等)
  • 不要用 rand()、Math.random():这些不是密码学安全的
  • 注意熵不足的场景:嵌入式设备、虚拟机、容器启动早期
  • 密钥生成:确保从高质量熵源获取足够的随机比特