随机数生成:真随机数生成器与伪随机数生成器,熵源选择

密钥安全的第一道关口,就是随机数的质量。你想想看,如果密钥是用一个可预测的序列生成的,那加密还有什么意义?这章我就专门聊聊随机数的生成——怎么区分真随机和伪随机,怎么选熵源,怎么在工程里落地。

我在做安全审计时见过不少事故,说白了都是随机数没整明白。

3.1 随机数的本质与密码学要求

密码学里说的随机数,和你在程序里用的 Math.random() 完全不是一回事。

它得满足三个硬性条件:

  • 不可预测性——就算知道过去输出的所有随机数,也没法推断出下一个。
  • 均匀分布——每一位是0或1的概率基本相等,不能有偏。
  • 独立性——任何两个随机数之间统计上无关。

实践中,真正能达到这三条的只有真随机数生成器(TRNG)。

核心原则:密钥材料必须来自不可预测的熵源。任何算法生成的伪随机数,如果种子被泄露,就是假的。

3.2 真随机数生成器(TRNG)

TRNG 从物理过程中提取随机性。比如:

  • 热噪声(电阻的约翰逊噪声)
  • 时钟抖动(晶振的微小漂移)
  • 量子效应(光子偏振)
  • 用户行为(鼠标移动、键盘时序)

我记得有一次做嵌入式密钥生成,芯片内置的 TRNG 采样率不够,导致密钥生成速度卡脖子。最后我们外挂了一个专用熵源芯片,才解决问题。

TRNG 的优点是真正的不可预测,但缺点是输出速率低、对硬件依赖强。

3.3 伪随机数生成器(PRNG)

PRNG 用确定性算法把短种子扩展成看似随机的长序列。密码学里用的叫 CSPRNG(密码学安全伪随机数生成器),比如:

  • Yarrow(FreeBSD 的 /dev/random)
  • ChaCha20 CSPRNG(Linux 5.6+ 的内核随机池)
  • Fortuna(在 Yarrow 基础上改进)
特性 TRNG CSPRNG
熵来源 物理过程 种子(来自于TRNG)
输出速率 慢(<100 Mbps) 快(>1 Gbps)
可预测性 不可预测 种子未知时不可预测
硬件依赖 高 低
典型用途 种子生成、一次性密钥 批量密钥、会话密钥

你在工程里碰到的 /dev/urandom 和 openssl rand,本质都是 CSPRNG。它们会定期从内核熵池里补充新熵。

千万别把 rand() 或 mt_rand() 用在密钥生成上。它们不是 CSPRNG,种子空间小到可以被暴力破解。我曾经见过一个项目用 time(NULL) 做种子,结果所有密钥都在同一秒内重复——简直是给攻击者送大礼。

3.4 熵源的选择与评估

熵源的质量直接决定密钥安全。我习惯按这三级评估:

  1. 硬件熵源——专用 TRNG 芯片、CPU 内置 DRNG(Intel RDRAND),最好用,但要注意后门风险。
  2. 系统熵源——Linux 的 /dev/random(阻塞模式)和 /dev/urandom(非阻塞)。我建议优先用 /dev/urandom,除非在系统刚启动时种子不够。
  3. 混合熵源——组合多个物理源(比如鼠标 + 麦克风 + 网络延迟),通过 Hash 提取公共熵。

怎么评估熵源够不够?看这个:

熵估值:理论上每比特输出应至少包含 0.5 位的真实熵。常用工具如 ent、Dieharder、NIST SP 800-90B 测试套件。我每次换硬件平台,都会先跑一遍 NIST 测试,卡住就不放行。

我曾经被一个问题坑过:某 ARM 板的硬件熵源在低功耗模式下输出全零。后来做了一票否决机制——连续采样 8 次如果全是 0 或 1,直接报错,不留隐含陷阱。

3.5 工程实践中的关键原则

  • 从不直接使用 TRNG 输出做密钥——经过 CSPRNG 混合,消除硬件偏置。
  • 定期 reseed——Linux 内核每秒会 reseed 一次,但如果你自己实现 CSPRNG,建议每生成 1MB 随机数就触发一次。
  • 禁止使用系统时间、PID、MAC 地址等作为唯一种子——这些都能被对手猜到。

一个小技巧:在应用层,可以调用 getrandom() 系统调用(Linux 3.17+),它比 /dev/urandom 更安全,而且不会因为文件描述符耗尽而出错。我新写的代码现在都用它。

下面这张图总结了我理解的随机数生成流程。记住:熵源是根,种子是干,CSPRNG 是枝,密钥是果。

随机数生成流程结构图 物理熵源 热噪声/量子效应 系统熵源 用户行为/网络抖动 TRNG 真随机比特流 种子生成 Hash提取 / NIST SP 800-90B CSPRNG ChaCha20 / Fortuna 密钥输出 AES-256 / ECC 混合熵源 → TRNG → 种子 → CSPRNG → 密钥 图:随机数生成在密钥管理中的完整链路

最后提醒一句:别在随机数上耍小聪明。用现成的系统 CSPRNG,配好熵源监控,这就是最稳妥的做法。我上个月还在审查一个代码,发现有人自己实现了一个 LCG 算法生成密钥……当时我就直接打回了。千万别重造劣质轮子。