内核把设备噪声转成密钥
即便一台很安静的服务器,在真正需要随机数之前也早就完成了播种,所以今天还会出问题的随机性事件几乎都不是「熵不够」,而是「状态被重放」:镜像里烤死的种子、被恢复的快照、回滚到上一次重播种之前的状态。内核并不朝你一滴一滴地滴熵;它把一点点设备噪声浓缩成一个生成器,然后无限地吐密钥流。 这个反转正是为什么围绕熵的大部分运维经验已经失效。盯着 entropy_avail、为取密钥去读 /dev/random、装一个补熵守护进程,这三件事针对的都是内核早已不再存在的稀缺;而真正会毁掉密钥的那件事——两台机器拿到同一个种子——在这些习惯里一个都看不出来。 熵池是输入,生成器是产物 内核把物理噪声混进来:中断时序抖动、设备延迟、平台自带的硬件随机数发生器,以及基于 CPU 的 jitterentropy;再用 BLAKE2s 把它们抽取进熵池。熵池给一组 ChaCha20 CRNG 播种,真正产出字节的是这些 CRNG。播种之后收集仍在继续,生成器也会被周期性重播种,但你已经吐出去的每一个字节,其强度由它出生时的那个状态决定,之后再发生什么都改不了。 一旦初始化完成,输出就是密钥流,而不是有限资源。entropy_avail 稳定在 256 位,也就是一个 ChaCha20 密钥的长度,而且你读它它也不会掉;读走 1 MiB 不消耗任何池内配额。random(4) 至今仍把 entropy_avail 描述成 0 到 4096 之间的取值、把 poolsize 描述成 Linux 2.6 之后固定 4096 位——正是这类文档让一条过期的告警一直活着。 /dev/random 不再是「更谨慎」的选择 从 Linux 5.6 起,/dev/random 在早期启动之外不再阻塞,其上的 O_NONBLOCK 也被忽略。那个独立的阻塞池已经消失,剩下的只是同一个生成器顶着历史名字。getrandom(2)(Linux 3.17 引入)默认从 urandom 源取数,只在那个源尚未初始化时阻塞;GRND_NONBLOCK 把这种阻塞变成 EAGAIN,GRND_RANDOM 则改为选择 random 源,也就是那个仍然可能无限期阻塞的源。 ...