一篇讨论 Unix/Linux 随机数设备的技术文章,又把一个老问题翻了出来:生成密钥、令牌、会话随机数时,到底该读 /dev/random,还是 /dev/urandom?
很多人的直觉是,名字里少了一个 u 的 /dev/random 更“真”,会阻塞也说明更谨慎。但这个直觉很容易误导人。初始化完成后的现代类 Unix 系统里,/dev/urandom 通常才是密码学随机数的推荐来源。
这事不只是手册页里的小差异。TLS 会话密钥、SSH 密钥、证书生成、容器启动、数据库令牌,都可能依赖系统随机源。选错接口,不一定更安全,却可能让服务卡住。
更麻烦的是,服务一旦卡住,工程团队常会找捷径。手工改熵计数、换弱随机函数、关闭某个安全检查,这些补丁看起来是在救火,实际可能是在挖坑。
误解在哪里:不是“真随机 vs 伪随机”
最常见的误解,是把 /dev/random 和 /dev/urandom 分成两类:前者是真随机,后者是伪随机。
这个说法太粗。历史上,两者都依赖内核里的密码学安全伪随机数生成器,也就是 CSPRNG。它不是普通 PRNG,设计目标就是让攻击者在不知道内部状态和种子的情况下,无法预测输出。
两者的核心差异,主要在阻塞行为。内核认为熵估计不足时,/dev/random 可能阻塞;/dev/urandom 在随机数生成器初始化完成后,通常不会因为这个估计值继续卡住调用者。
| 对比项 | /dev/random | /dev/urandom | 更准确的理解 |
|---|---|---|---|
| 输出来源 | 内核随机数机制 / CSPRNG | 内核随机数机制 / CSPRNG | 不是一个纯真随机、一个普通伪随机 |
| 主要差别 | 可能因熵估计阻塞 | 初始化后通常不阻塞 | 阻塞不是安全性的同义词 |
| 常规密码学用途 | 不一定更合适 | 通常是推荐来源 | 前提是现代系统且已初始化 |
| 主要风险 | 启动慢、服务卡死、批量部署受阻 | 早期启动阶段需确认初始化 | 两边都不能脱离系统状态谈绝对安全 |
熵也不是仓库里的库存,不能像数硬币一样实时清点。内核只能估计键盘、磁盘、网络、硬件随机源等事件里有多少不可预测性。
所以,“熵用完了”经常被误用。它听起来像随机数被消耗完了,其实更接近一个保守估计值下降了。
只要 CSPRNG 已经拿到足够高质量的种子,约 256 bit 熵就足以支撑长期的计算安全输出。持续注入熵当然有益,但这不等于估计值一低,系统就必须停下来等“新随机”。
这也是这场争论的主线:问题不是“真随机是否更高级”,而是阻塞式 /dev/random 在实际密码学场景中,是否真的比 /dev/urandom 更安全、更合适。
我的判断是,通常不是。
机制要看边界:初始化完成后,/dev/urandom 才好用
这里要留一个重要边界:不能把 /dev/urandom 写成任何时候、任何系统、任何启动阶段都绝对安全。
更稳妥的说法是:在现代系统、内核随机数生成器已经初始化、系统随机机制正常的情况下,/dev/urandom 通常适合密码学用途。
早期启动阶段是例外风险。比如极简系统刚启动、嵌入式设备缺少随机事件、虚拟机镜像被克隆后没有重新播种,这些场景都需要额外检查。
Linux 引入 getrandom(2) 的现实意义,就在这里。程序可以等待内核随机数生成器初始化完成,而不是自己直接读设备文件,再猜当前状态是否安全。
对开发者来说,动作应该更明确:
- 写新代码时,优先用操作系统或语言标准库提供的密码学安全随机接口。
- Linux 上,优先考虑 getrandom(2) 或语言运行时对它的封装。
- 不要为了“看起来更真”,默认去读 /dev/random。
- 不要自己写 PRNG 来替代系统随机源。
对安全工程师来说,评审重点也要换。别只问“用了 random 还是 urandom”,更要问:随机数生成器是否已初始化?虚拟机克隆后是否重新播种?容器和极简镜像有没有正确继承系统随机源?
这比盯着设备文件名更有用。
真正的成本:阻塞会逼出更危险的绕过
/dev/random 的问题,常常不在密码学论文里,而在生产系统里。
云主机批量启动、虚拟机里生成 SSH 或 PGP 密钥、Web 服务创建临时密钥,都可能碰到随机源阻塞。系统卡住时,最先承压的是值班工程师和发布流程。
这时,组织里的安全决策会变形。一个阻塞问题如果挡住上线,团队很可能先追求“让服务起来”。于是,弱随机函数、手工调参数、关闭安全功能,就有了进入生产环境的机会。
这就是 /dev/random 被高估的地方。它把安全感放在“等待”这个动作上,但等待本身并不自动制造更高质量的密码学随机数。等待还会制造可用性压力。
相关团队可以按对象分两类处理:
| 角色 | 应该做什么 | 不该做什么 |
|---|---|---|
| Linux/Unix 系统开发者 | 用 getrandom(2)、arc4random、SecRandomCopyBytes 或语言安全随机接口 | 直接在业务代码里纠结读 /dev/random 还是 /dev/urandom |
| 运维与安全工程师 | 检查启动阶段初始化、镜像克隆播种、容器随机源继承 | 用改熵计数、换弱随机函数来解决阻塞 |
不同系统的推荐接口不完全一样。OpenBSD 长期推荐 arc4random 系列接口;Apple 平台有 SecRandomCopyBytes;Linux 上更常见的路线是 getrandom(2) 及其语言封装。
这些接口的方向是一致的:把随机数复杂性收进内核和基础库,减少业务工程师直接碰设备文件的机会。
接下来最该观察的,也不是哪篇文章把 /dev/random 说得更神秘,而是发行版、语言运行时和安全库有没有继续减少误用空间。比如默认 API 是否会等待初始化完成,容器和虚拟机环境是否处理好播种,文档是否还在暗示“会阻塞就更安全”。
回到开头那个问题:该不该迷信会阻塞的 /dev/random?
不该。安全工程里,可预测性是敌人,误用也是敌人。把接口选得更难用,不会自动让系统更安全。
