有不少关于 /dev/urandom
和 /dev/random
的流言在坊间不断流传。然而流言终究是流言。html
本篇文章里针对的都是近来的 Linux 操做系统,其它类 Unix 操做系统不在讨论范围内。linux
/dev/urandom
不安全。加密用途必须使用 /dev/random
git
事实:/dev/urandom
才是类 Unix 操做系统下推荐的加密种子。程序员
/dev/urandom
是伪随机数生成器(PRND),而 /dev/random
是“真”随机数生成器。github
事实:它们二者本质上用的是同一种 CSPRNG (一种密码学伪随机数生成器)。它们之间细微的差异和“真”“不真”随机彻底无关。(参见:“Linux 随机数生成器的构架”一节)算法
/dev/random
在任何状况下都是密码学应用更好地选择。即使 /dev/urandom
也一样安全,咱们仍是不该该用它。shell
事实:/dev/random
有个很恶心人的问题:它是阻塞的。(参见:“阻塞有什么问题?”一节)(LCTT 译注:意味着请求都得逐个执行,等待前一个请求完成)安全
但阻塞不是好事吗!/dev/random
只会给出电脑收集的信息熵足以支持的随机量。/dev/urandom
在用完了全部熵的状况下还会不断吐出不安全的随机数给你。ruby
事实:这是误解。就算咱们不去考虑应用层面后续对随机种子的用法,“用完信息熵池”这个概念自己就不存在。仅仅 256 位的熵就足以生成计算上安全的随机数很长、很长的一段时间了。(参见:“那熵池快空了的状况呢?”一节)服务器
问题的关键还在后头:/dev/random
怎么知道有系统会多少可用的信息熵?接着看!
但密码学家总是讨论从新选种子(re-seeding)。这难道不和上一条冲突吗?
事实:你说的也没错!某种程度上吧。确实,随机数生成器一直在使用系统信息熵的状态从新选种。但这么作(一部分)是由于别的缘由。(参见:“从新选种”一节)
这样说吧,我没有说引入新的信息熵是坏的。更多的熵确定更好。我只是说在熵池低的时候阻塞是不必的。
好,就算你说的都对,可是 /dev/(u)random
的 man 页面和你说的也不同啊!到底有没有专家赞成你说的这堆啊?
事实:其实 man 页面和我说的不冲突。它看似好像在说 /dev/urandom
对密码学用途来讲不安全,但若是你真的理解这堆密码学术语你就知道它说的并非这个意思。(参见:“random 和 urandom 的 man 页面”一节)
man 页面确实说在一些状况下推荐使用 /dev/random
(我以为也没问题,但绝对不是说必要的),但它也推荐在大多数“通常”的密码学应用下使用 /dev/urandom
。
虽然诉诸权威通常来讲不是好事,但在密码学这么严肃的事情上,和专家统一意见是颇有必要的。
因此说呢,还确实有一些专家和个人一件事一致的:/dev/urandom
就应该是类 UNIX 操做系统下密码学应用的首选。显然的,是他们的观点说服了我而不是反过来的。(参见:“正道”一节)
难以相信吗?以为我确定错了?读下去看我能不能说服你。
我尝试不讲过高深的东西,可是有两点内容必须先提一下才能让咱们接着论证观点。
首当其冲的,什么是随机性,或者更准确地:咱们在探讨什么样的随机性?(参见:“真随机”一节)
另一点很重要的是,我没有尝试以说教的态度对大家写这段话。我写这篇文章是为了往后能够在讨论起的时候指给别人看。比 140 字长(LCTT 译注:推特长度)。这样我就不用一遍遍重复个人观点了。能把论点磨炼成一篇文章自己就颇有助于未来的讨论。(参见:“你是在说我笨?!”一节)
而且我很是乐意听到不同的观点。但我只是认为单单地说 /dev/urandom
坏是不够的。你得能指出到底有什么问题,而且剖析它们。
绝对没有!
事实上我本身也相信了 “/dev/urandom
是不安全的” 好些年。这几乎不是咱们的错,由于那么德高望重的人在 Usenet、论坛、推特上跟咱们重复这个观点。甚至连 man 手册都似是而非地说着。咱们当年怎么可能鄙视诸如“信息熵过低了”这种看上去就很让人信服的观点呢?(参见:“random 和 urandom 的 man 页面”一节)
整个流言之因此如此广为流传不是由于人们太蠢,而是由于但凡是有点关于信息熵和密码学概念的人都会以为这个说法颇有道理。直觉彷佛都在告诉咱们这流言讲的颇有道理。很不幸直觉在密码学里一般无论用,此次也同样。
随机数是“真正随机”是什么意思?
我不想搞的太复杂以致于变成哲学范畴的东西。这种讨论很容易走偏由于对于随机模型你们见仁见智,讨论很快变得毫无心义。
在我看来“真随机”的“试金石”是量子效应。一个光子穿过或不穿过一个半透镜。或者观察一个放射性粒子衰变。这类东西是现实世界最接近真随机的东西。固然,有些人也不相信这类过程是真随机的,或者这个世界根本不存在任何随机性。这个就百家争鸣了,我也很差多说什么了。
密码学家通常都会经过不去讨论什么是“真随机”来避免这种哲学辩论。他们更关心的是不可预测性。只要没有任何方法能猜出下一个随机数就能够了。因此当你以密码学应用为前提讨论一个随机数好很差的时候,在我看来这才是最重要的。
不管如何,我不怎么关心“哲学上安全”的随机数,这也包括别人嘴里的“真”随机数。
但就让咱们退一步说,你有了一个“真”随机变量。你下一步作什么呢?
你把它们打印出来而后挂在墙上来展现量子宇宙的美与和谐?牛逼!我支持你。
可是等等,你说你要用它们?作密码学用途?额,那这就废了,由于这事情就有点复杂了。
事情是这样的,你的真随机、量子力学加护的随机数即将被用进不理想的现实世界算法里去。
由于咱们使用的几乎全部的算法都并非信息论安全性的。它们“只能”提供计算意义上的安全。我能想到为数很少的例外就只有 Shamir 密钥分享和一次性密码本(OTP)算法。而且就算前者是名副其实的(若是你实际打算用的话),后者则毫无可行性可言。
但全部那些大名鼎鼎的密码学算法,AES、RSA、Diffie-Hellman、椭圆曲线,还有全部那些加密软件包,OpenSSL、GnuTLS、Keyczar、你的操做系统的加密 API,都仅仅是计算意义上安全的。
那区别是什么呢?信息论安全的算法确定是安全的,绝对是,其它那些的算法均可能在理论上被拥有无限计算力的穷举破解。咱们依然愉快地使用它们是由于全世界的计算机加起来都不可能在宇宙年龄的时间里破解,至少如今是这样。而这就是咱们文章里说的“不安全”。
除非哪一个聪明的家伙破解了算法自己 —— 在只须要更少许计算力、在今天可实现的计算力的状况下。这也是每一个密码学家求之不得的圣杯:破解 AES 自己、破解 RSA 自己等等。
因此如今咱们来到了更底层的东西:随机数生成器,你坚持要“真随机”而不是“伪随机”。可是没过一下子你的真随机数就被喂进了你极为鄙视的伪随机算法里了!
真相是,若是咱们最早进的哈希算法被破解了,或者最早进的分组加密算法被破解了,你获得的这些“哲学上不安全”的随机数甚至无所谓了,由于反正你也没有安全的应用方法了。
因此把计算性上安全的随机数喂给你的仅仅是计算性上安全的算法就能够了,换而言之,用 /dev/urandom
。
你对内核的随机数生成器的理解极可能是像这样的:
“真正的随机性”,尽管可能有点瑕疵,进入操做系统而后它的熵马上被加入内部熵计数器。而后通过“矫偏”和“漂白”以后它进入内核的熵池,而后 /dev/random
和 /dev/urandom
从里面生成随机数。
“真”随机数生成器,/dev/random
,直接从池里选出随机数,若是熵计数器表示能知足须要的数字大小,那就吐出数字而且减小熵计数。若是不够的话,它会阻塞程序直至有足够的熵进入系统。
这里很重要一环是 /dev/random
几乎只是仅通过必要的“漂白”后就直接把那些进入系统的随机性吐了出来,不经扭曲。
而对 /dev/urandom
来讲,事情是同样的。除了当没有足够的熵的时候,它不会阻塞,而会从一直在运行的伪随机数生成器(固然,是密码学安全的,CSPRNG)里吐出“低质量”的随机数。这个 CSPRNG 只会用“真随机数”生成种子一次(或者好几回,这不重要),但你不能特别相信它。
在这种对随机数生成的理解下,不少人会以为在 Linux 下尽可能避免 /dev/urandom
看上去有那么点道理。
由于要么你有足够多的熵,你会至关于用了 /dev/random
。要么没有,那你就会从几乎没有高熵输入的 CSPRNG 那里获得一个低质量的随机数。
看上去很邪恶是吧?很不幸的是这种见解是彻底错误的。实际上,随机数生成器的构架更像是下面这样的。
你看到最大的区别了吗?CSPRNG 并非和随机数生成器一块儿跑的,它在 /dev/urandom
须要输出但熵不够的时候进行填充。CSPRNG 是整个随机数生成过程的内部组件之一。历来就没有什么 /dev/random
直接从池里输出纯纯的随机性。每一个随机源的输入都在 CSPRNG 里充分混合和散列过了,这一切都发生在实际变成一个随机数,被 /dev/urandom
或者 /dev/random
吐出去以前。
另一个重要的区别是这里没有熵计数器的任何事情,只有预估。一个源给你的熵的量并非什么很明确能直接获得的数字。你得预估它。注意,若是你太乐观地预估了它,那 /dev/random
最重要的特性——只给出熵容许的随机量——就荡然无存了。很不幸的,预估熵的量是很困难的。
这是个很粗糙的简化。实际上不只有一个,而是三个熵池。一个主池,另外一个给
/dev/random
,还有一个给/dev/urandom
,后二者依靠从主池里获取熵。这三个池都有各自的熵计数器,但二级池(后两个)的计数器基本都在 0 附近,而“新鲜”的熵总在须要的时候从主池流过来。同时还有好多混合和回流进系统在同时进行。整个过程对于这篇文档来讲都过于复杂了,咱们跳过。
Linux 内核只使用事件的到达时间来预估熵的量。根据模型,它经过多项式插值来预估实际的到达时间有多“出乎意料”。这种多项式插值的方法究竟是不是好的预估熵量的方法自己就是个问题。同时硬件状况会不会以某种特定的方式影响到达时间也是个问题。而全部硬件的取样率也是个问题,由于这基本上就直接决定了随机数到达时间的颗粒度。
说到最后,至少如今看来,内核的熵预估仍是不错的。这也意味着它比较保守。有些人会具体地讨论它有多好,这都超出个人脑容量了。就算这样,若是你坚持不想在没有足够多的熵的状况下吐出随机数,那你看到这里可能还会有一丝紧张。我睡的就很香了,由于我不关心熵预估什么的。
最后要明确一下:/dev/random
和 /dev/urandom
都是被同一个 CSPRNG 饲喂的。只有它们在用完各自熵池(根据某种预估标准)的时候,它们的行为会不一样:/dev/random
阻塞,/dev/urandom
不阻塞。
在 Linux 4.8 里,/dev/random
和 /dev/urandom
的等价性被放弃了。如今 /dev/urandom
的输出不来自于熵池,而是直接从 CSPRNG 来。
咱们很快会理解为何这不是一个安全问题。(参见:“CSPRNG 没问题”一节)
你有没有须要等着 /dev/random
来吐随机数?好比在虚拟机里生成一个 PGP 密钥?或者访问一个在生成会话密钥的网站?
这些都是问题。阻塞本质上会下降可用性。换而言之你的系统不干你让它干的事情。不用我说,这是很差的。要是它不干活你干吗搭建它呢?
我在工厂自动化里作过和安全相关的系统。猜猜看安全系统失效的主要缘由是什么?操做问题。就这么简单。不少安全措施的流程让工人恼火了。好比时间太长,或者太不方便。你要知道人很会找捷径来“解决”问题。
但其实有个更深入的问题:人们不喜欢被打断。它们会找一些绕过的方法,把一些诡异的东西接在一块儿仅仅由于这样能用。通常人根本不知道什么密码学什么乱七八糟的,至少正常的人是这样吧。
为何不由止调用 random()
?为何不随便在论坛上找我的告诉你用写奇异的 ioctl 来增长熵计数器呢?为何不干脆就把 SSL 加密给关了算了呢?
到头来若是东西太难用的话,你的用户就会被迫开始作一些下降系统安全性的事情——你甚至不知道它们会作些什么。
咱们很容易会忽视可用性之类的重要性。毕竟安全第一对吧?因此比起牺牲安全,不可用、难用、不方便都是次要的?
这种二元对立的想法是错的。阻塞不必定就安全了。正如咱们看到的,/dev/urandom
直接从 CSPRNG 里给你同样好的随机数。用它很差吗!
如今状况听上去很惨淡。若是连高质量的 /dev/random
都是从一个 CSPRNG 里来的,咱们怎么敢在高安全性的需求上使用它呢?
实际上,“看上去随机”是现存大多数密码学基础组件的基本要求。若是你观察一个密码学哈希的输出,它必定得和随机的字符串不可区分,密码学家才会承认这个算法。若是你生成一个分组加密,它的输出(在你不知道密钥的状况下)也必须和随机数据不可区分才行。
若是任何人能比暴力穷举要更有效地破解一个加密,好比它利用了某些 CSPRNG 伪随机的弱点,那这就又是老一套了:一切都废了,也别谈后面的了。分组加密、哈希,一切都是基于某个数学算法,好比 CSPRNG。因此别惧怕,到头来都同样。
毫无影响。
加密算法的根基创建在攻击者不能预测输出上,只要最一开始有足够的随机性(熵)就好了。“足够”的下限能够是 256 位,不须要更多了。
介于咱们一直在很随意的使用“熵”这个概念,我用“位”来量化随机性但愿读者不要太在乎细节。像咱们以前讨论的那样,内核的随机数生成器甚至无法精确地知道进入系统的熵的量。只有一个预估。并且这个预估的准确性到底怎么样也没人知道。
但若是熵这么不重要,为何还要有新的熵一直被收进随机数生成器里呢?
djb 提到 太多的熵甚至可能会起到反效果。
首先,通常不会这样。若是你有不少随机性能够拿来用,用就对了!
但随机数生成器时不时要从新选种还有别的缘由:
想象一下若是有个攻击者获取了你随机数生成器的全部内部状态。这是最坏的状况了,本质上你的一切都暴露给攻击者了。
你已经凉了,由于攻击者能够计算出全部将来会被输出的随机数了。
可是,若是不断有新的熵被混进系统,那内部状态会再一次变得随机起来。因此随机数生成器被设计成这样有些“自愈”能力。
但这是在给内部状态引入新的熵,这和阻塞输出没有任何关系。
这两个 man 页面在吓唬程序员方面颇有建树:
从
/dev/urandom
读取数据不会由于须要更多熵而阻塞。这样的结果是,若是熵池里没有足够多的熵,取决于驱动使用的算法,返回的数值在理论上有被密码学攻击的可能性。发动这样攻击的步骤并无出如今任何公开文献当中,但这样的攻击从理论上讲是可能存在的。若是你的应用担忧这类状况,你应该使用/dev/random
。
实际上已经有
/dev/random
和/dev/urandom
的 Linux 内核 man 页面的更新版本。不幸的是,随便一个网络搜索出现我在结果顶部的仍然是旧的、有缺陷的版本。此外,许多 Linux 发行版仍在发布旧的 man 页面。因此不幸的是,这一节须要在这篇文章中保留更长的时间。我很期待删除这一节!
没有“公开的文献”描述,可是 NSA 的小卖部里确定卖这种攻击手段是吧?若是你真的真的很担忧(你应该很担忧),那就用 /dev/random
而后全部问题都没了?
然而事实是,可能某个什么情报局有这种攻击,或者某个什么邪恶黑客组织找到了方法。但若是咱们就直接假设这种攻击必定存在也是不合理的。
并且就算你想给本身一个安心,我要给你泼个冷水:AES、SHA-3 或者其它什么常见的加密算法也没有“公开文献记述”的攻击手段。难道你也不用这几个加密算法了?这显然是好笑的。
咱们在回到 man 页面说:“使用 /dev/random
”。咱们已经知道了,虽然 /dev/urandom
不阻塞,可是它的随机数和 /dev/random
都是从同一个 CSPRNG 里来的。
若是你真的须要信息论安全性的随机数(你不须要的,相信我),那才有可能成为惟一一个你须要等足够熵进入 CSPRNG 的理由。并且你也不能用 /dev/random
。
man 页面有毒,就这样。但至少它还稍稍挽回了一下本身:
若是你不肯定该用
/dev/random
仍是/dev/urandom
,那你可能应该用后者。一般来讲,除了须要长期使用的 GPG/SSL/SSH 密钥之外,你总该使用/dev/urandom
。
该手册页的当前更新版本绝不含糊地说:
/dev/random
接口被认为是遗留接口,而且/dev/urandom
在全部用例中都是首选和足够的,除了在启动早期须要随机性的应用程序;对于这些应用程序,必须替代使用getrandom(2)
,由于它将阻塞,直到熵池初始化完成。
行。我以为不必,但若是你真的要用 /dev/random
来生成 “长期使用的密钥”,用就是了也没人拦着!你可能须要等几秒钟或者敲几下键盘来增长熵,但这没什么问题。
但求求大家,不要就由于“你想更安全点”就让连个邮件服务器要挂起半天。
本篇文章里的观点显然在互联网上是“小众”的。但若是问一个真正的密码学家,你很难找到一个认同阻塞 /dev/random
的人。
好比咱们看看 Daniel Bernstein(即著名的 djb)的见解:
咱们密码学家对这种胡乱迷信行为表示不负责。你想一想,写
/dev/random
man 页面的人好像同时相信:
- (1) 咱们不知道如何用一个 256 位长的
/dev/random
的输出来生成一个无限长的随机密钥串流(这是咱们须要/dev/urandom
吐出来的),但与此同时- (2) 咱们却知道怎么用单个密钥来加密一条消息(这是 SSL,PGP 之类干的事情)
对密码学家来讲这甚至都很差笑了
或者 Thomas Pornin 的见解,他也是我在 stackexchange 上见过最乐于助人的一位:
简单来讲,是的。展开说,答案仍是同样。
/dev/urandom
生成的数据能够说和真随机彻底没法区分,至少在现有科技水平下。使用比/dev/urandom
“更好的“随机性毫无心义,除非你在使用极为罕见的“信息论安全”的加密算法。这确定不是你的状况,否则你早就说了。urandom 的 man 页面多多少少有些误导人,或者干脆能够说是错的——特别是当它说
/dev/urandom
会“用完熵”以及 “/dev/random
是更好的”那几句话;
或者 Thomas Ptacek 的见解,他不设计密码算法或者密码学系统,但他是一家名声在外的安全咨询公司的创始人,这家公司负责不少渗透和破解烂密码学算法的测试:
用 urandom。用 urandom。用 urandom。用 urandom。用 urandom。
/dev/urandom
不是完美的,问题分两层:
在 Linux 上,不像 FreeBSD,/dev/urandom
永远不阻塞。记得安全性取决于某个最一开始决定的随机性?种子?
Linux 的 /dev/urandom
会很乐意给你吐点不怎么随机的随机数,甚至在内核有机会收集一丁点熵以前。何时有这种状况?当你系统刚刚启动的时候。
FreeBSD 的行为更正确点:/dev/random
和 /dev/urandom
是同样的,在系统启动的时候 /dev/random
会阻塞到有足够的熵为止,而后它们都不再阻塞了。
与此同时 Linux 实行了一个新的系统调用,最先由 OpenBSD 引入叫
getentrypy(2)
,在 Linux 下这个叫getrandom(2)
。这个系统调用有着上述正确的行为:阻塞到有足够的熵为止,而后不再阻塞了。固然,这是个系统调用,而不是一个字节设备(LCTT 译注:不在/dev/
下),因此它在 shell 或者别的脚本语言里没那么容易获取。这个系统调用 自 Linux 3.17 起存在。
在 Linux 上其实这个问题不太大,由于 Linux 发行版会在启动的过程当中保存一点随机数(这发生在已经有一些熵以后,由于启动程序不会在按下电源的一瞬间就开始运行)到一个种子文件中,以便系统下次启动的时候读取。因此每次启动的时候系统都会从上一次会话里带一点随机性过来。
显然这比不上在关机脚本里写入一些随机种子,由于这样的显然就有更多熵能够操做了。但这样作显而易见的好处就是它不用关心系统是否是正确关机了,好比可能你系统崩溃了。
并且这种作法在你真正第一次启动系统的时候也无法帮你随机,不过好在 Linux 系统安装程序通常会保存一个种子文件,因此基本上问题不大。
虚拟机是另一层问题。由于用户喜欢克隆它们,或者恢复到某个以前的状态。这种状况下那个种子文件就帮不到你了。
但解决方案依然和用 /dev/random
不要紧,而是你应该正确的给每一个克隆或者恢复的镜像从新生成种子文件。
别问,问就是用 /dev/urandom
!
做者:Thomas Hühn 译者:Moelf 校对:wxy