熵源是所有安全随机数生成器的根。如果熵源质量不过关,上层加密协议做得再严谨也可能被预测性击穿。这里的熵不是热力学概念,而是信息论中的不确定性:攻击者无法提前猜出的程度。系统需要从物理噪声、硬件行为、用户操作和IO时序中采集这种不确定性,再经过评估和后处理,才能形成可用的随机数种子。

不少人把/dev/urandom直接当成熵源,这其实混淆了熵源和随机数发生器输出。熵源是原始的不完美信号,随机数发生器是对原始熵进行压缩、混合和扩展后的结果。理解二者的边界,对后续采集和评估非常关键。
一、熵源从哪里来:常见采集路径
操作系统通常会把熵源分成三类。第一类是物理噪声源,例如CPU上的热噪声、振荡器抖动、半导体雪崩噪声,这些信号天然带有随机性。硬件随机数发生器如Intel RDSEED、ARM的TRNG模块会把这类噪声数字化,通过专用寄存器暴露给内核。
第二类是系统事件源,包括中断到达时间、磁盘寻道延迟、网络包到达间隔等。这类源并不是完全随机,但它们在真实负载下表现出明显的不可预测性。Linux内核启动时会用中断时间戳填充熵池,鼠标移动、键盘敲击也可以贡献额外数据。
第三类是用户交互源,例如Web应用里的鼠标轨迹、触摸坐标、按键节奏。这类熵的质量与部署环境关系很大,在服务器上可能完全没有用户交互,所以不能作为唯一依赖。理想的采集策略是分层设计:硬件RNG优先,内核事件持续补充,用户交互作为最后一道保障。
采集硬件熵的接口并不复杂。Linux下如果存在硬件随机数发生器,可以读取/dev/hwrng来获得原始数据。下面这段C代码演示了读取32字节硬件熵并打印为十六进制。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
unsigned char buf[32];
int fd = open("/dev/hwrng", O_RDONLY);
if (fd < 0) return 1;
if (read(fd, buf, sizeof(buf)) != sizeof(buf)) return 1;
close(fd);
for (int i = 0; i < 32; i++) {
printf("%02x", buf[i]);
}
printf("\n");
return 0;
}
需要注意,/dev/hwrng返回的数据可能已经经过硬件厂商的简单处理,也可能直接暴露原始位流。具体行为取决于驱动和芯片。无论哪种情况,进入熵池前都应该先做质量评估,而不是无脑信任。
二、熵质量评估的核心指标
评估熵源不能只靠统计随机性。一个序列可能通过所有常见随机性测试,但仍然是可预测的。密码学上更看重的是最小熵,也就是攻击者在最优猜测下成功预测一个输出的概率。最小熵定义为负的以2为底的最大单值概率对数。它的值越小,意味着最坏情况越不可预测。
最小熵与香农熵的区别值得展开。香农熵描述平均不确定性,适合通信编码场景;而安全场景关心最坏情况。假设一个字节源有50%概率输出0x00,其余255个值均匀分配,香农熵可能不低,但最小熵只有1,因为攻击者只要猜0x00就有一半成功率。因此评估熵源时,必须计算最小熵而不是平均熵。
工程上常见的评估标准包括NIST SP 800-90B和德国AIS 31。SP 800-90B把熵源划分成独立同分布和非独立同分布两类,分别用不同的统计方法估算最小熵。AIS 31对物理随机数发生器提出了在线测试和离线测试要求,例如重复计数测试、扑克测试、游程测试。在线测试的作用是在设备运行过程中持续发现熵源退化。
下面是一个简单的最小熵估算方法。它统计样本中出现概率最高的值,然后用公式换算出每符号最小熵。
import math
from collections import Counter
def estimate_min_entropy(samples):
total = len(samples)
counts = Counter(samples)
max_prob = max(counts.values()) / total
return -math.log2(max_prob)
data = bytes.fromhex("00ff0a7b3c9d15e2")
print(estimate_min_entropy(data))
这段代码仅适用于独立同分布假设。对于中断时间戳这类序列相关数据,需要先做去重或降采样,否则估算结果会偏乐观。实际产品中应当使用经过认证的测试套件,而不是自己写几十行代码就下结论。
三、工程实现中的常见误区与设计建议
第一个误区是把熵源直接当作密钥或随机数使用。原始熵通常存在偏差和相关性,直接使用等于把弱点暴露给攻击者。正确做法是让原始熵经过CSPRNG后处理。CSPRNG可以压缩大量低质量输入,输出统计上均匀且不可回溯的随机序列,但不能凭空创造熵,其安全性依然由输入熵的总量决定。
第二个误区是只依赖单一熵源。有些嵌入式设备只读取一个ADC引脚噪声,某天该引脚受到电磁干扰或硬件老化,随机性会迅速恶化。多源混合可以降低单点故障风险。最简单的混合方式是异或,但异或的前提是多个源之间尽量独立。更稳健的做法是把各源数据送入带密钥的哈希函数或CSPRNG进行再处理。
下面这段代码展示了一个小规模熵池的混合思路:用环形缓冲和异或操作把不同来源的数据搅拌在一起,避免某一段数据覆盖全部历史。
#include <stdint.h>
#include <string.h>
#define POOL_SIZE 64
static uint8_t entropy_pool[POOL_SIZE];
static size_t pool_pos = 0;
void mix_entropy(const uint8_t *src, size_t len) {
for (size_t i = 0; i < len; i++) {
entropy_pool[pool_pos % POOL_SIZE] ^= src[i];
pool_pos++;
}
}
第三个误区是部署后不再评估。熵源不是一成不变的,温度、电压、固件更新、虚拟化迁移都可能改变其统计特性。应当把在线健康测试作为长期运行的一部分。如果重复计数测试连续失败,就应暂停使用该熵源并产生告警。对于云主机,还要特别注意虚拟化环境中的熵源可能比物理机弱,务必结合宿主机提供的virtio-rng或硬件随机数支持。
最后,整个随机数生成链路的设计建议可以总结为:采集阶段尽量多源,评估阶段以最小熵为准,使用阶段依赖CSPRNG输出,运行阶段持续监控。把这四件事做好,熵源才能真正成为安全系统的可信起点。