当前位置: 首页 > 科技观察

关于-dev-urandom的神话破灭

时间:2023-03-15 14:57:28 科技观察

有很多关于/dev/urandom和/dev/random的谣言。但谣言就是谣言。本文针对近期的Linux操作系统,其他类Unix操作系统不在讨论范围内。/dev/urandom不安全。您必须使用/dev/random进行加密事实:/dev/urandom是推荐用于类Unix操作系统的加密种子。/dev/urandom是伪随机数生成器(PRND),而/dev/random是“真”随机数生成器。事实:它们都使用本质上相同的CSPRNG(一种加密伪随机数生成器)。它们之间的细微差别与“真”和“非真”随机性无关。(参见:“Linux随机数生成器的体系结构”部分)/dev/random在任何情况下都是加密应用程序的更好选择。即使/dev/urandom同样安全,我们也不应该使用它。事实:/dev/random有一个讨厌的问题:它会阻塞。(参见:“阻塞有什么问题?”部分)(LCTT译注:意思是请求必须一个一个执行,等待上一个请求完成)但是阻塞不是好事吗!/dev/random只会给出计算机有足够的熵来支持的随机性。/dev/urandom将在熵耗尽时继续向您吐出不安全的随机数。事实:这是一种误解。即使我们不考虑后续在应用层面使用随机种子,“信息熵池耗尽”这个概念本身也不存在。仅256位的熵就足以在很长很长一段时间内生成计算安全的随机数。(参见:“当熵池几乎为空时怎么办?”部分)问题的关键还在后面:/dev/random如何知道系统中有多少信息熵可用?一直在看!但是密码学家一直在谈论重新播种。这不和上一个冲突吗?事实:你是对的!不知何故。实际上,随机数生成器会不断地使用系统熵的状态重新播种。但它这样做(部分)是出于其他原因。(参见:“重新播种”部分)这样说,我并不是说引入新的信息熵是不好的。更多的熵肯定更好。我只是说当熵池很低时,阻塞是不必要的。好吧,即使你说的是对的,/dev/(u)random的手册页和你说的不一样!有专家同意你说的吗?事实:其实manpage和我说的并不冲突。这似乎是在说/dev/urandom对于加密目的来说是不安全的,但如果你真的理解这一堆加密术语,你就会知道这并不是那个意思。(参见:“随机和urandom的手册页”部分)手册页确实说在某些情况下建议使用/dev/random(我认为这很好,但绝对没有必要),但它也建议使用/dev/random在大多数情况下,将/dev/urandom用于“普通”加密应用程序。虽然诉诸权威通常不是一件好事,但在像密码学这样严肃的问题上,与专家达成一致是很有必要的。所以,确实有一些专家同意我的观点:/dev/urandom应该是类UNIX操作系统下密码学应用程序的选择。显然,是他们的观点说服了我,而不是相反。(参见:“正确的道路”部分)难以置信?认为我一定是错的?继续读下去,看看我能否说服你。我尽量不谈太高级的东西,但在我们继续争论之前,有两件事必须提到。首先,什么是随机性,或者更准确地说:我们在谈论什么样的随机性?(参见:“ReallyRandom”部分)同样重要的是,我不是试图以说教的方式向您写这篇文章。写这篇文章是为了以后讨论的时候可以给别人指出来。超过140个字符。这样我就不必一遍又一遍地重复我的观点。能够将一个论点磨练成一篇文章本身对未来的讨论非常有帮助。(参见:“你在说我傻吗?!”部分)我很想听听不同的观点。但我认为仅仅说/dev/urandom不好是不够的。你必须能够查明哪里出了问题并剖析它们。你说我傻吗?!绝对不!多年来,我实际上一直相信“/dev/urandom不安全”。这几乎不是我们的错,因为有很多受人尊敬的人在Usenet和论坛上向我们重复这一观点。甚至手册页也是合理的。当年“信息熵太低”这样看似有说服力的观点,我们怎能不屑一顾?(参见:“random和urandom的手册页”部分。)整个神话如此广泛传播的原因不是因为人们愚蠢,而是因为任何对熵和密码学有一点概念的人都会发现它是合理的。直觉似乎告诉我们,这个传闻很有道理。不幸的是,直觉通常在密码学中不起作用,这次也不行。随机数是“真正随机的”是什么意思?我不想让事情变得如此复杂以至于它们变得哲学化。这种讨论很容易误入歧途,因为每个人对随机模型的看法不同,讨论很快就变得毫无意义。在我看来,“真正随机”的“试金石”是量子效应。光子通过或不通过半反射镜。或者观察放射性粒子的衰变。这种事情是现实世界最接近真正随机性的事情。当然,也有人不相信这样的过程是真正随机的,或者世界上根本就没有随机性。这是争论的话题,我也不多说。密码学家通常通过不讨论什么是“真正随机的”来避免这种哲学辩论。他们更关心不可预测性。只要无法猜测下一个随机数,这就很好。所以当你根据密码学的应用来讨论一个随机数好不好用的时候,在我看来这是最重要的。无论如何,我不太关心“哲学上安全”的随机数,正如其他人所说,这包括“真正的”随机数。两个安全,一个有用但是让我们退一步说,你有一个“真正的”随机变量。下一步你会做什么你把它们打印出来挂在墙上来展示量子宇宙的美丽与和谐?惊人的!我支持你。但是等等,你说你要使用它们?出于密码学目的?嗯,那就没用了,因为这件事有点复杂。事情是这样的,你真正随机的、受量子力学保护的随机数将被用于次优的现实世界算法中。因为我们使用的算法几乎都不是信息论安全的。它们“仅”提供计算意义上的安全性。我能想到的少数例外是Shamir密钥共享和一次一密(OTP)算法。而即使前者名副其实(如果你真的打算用的话),后者也根本行不通。但是所有那些大牌加密算法,AES、RSA、Diffie-Hellman、椭圆曲线,以及所有那些加密包、OpenSSL、GnuTLS、Keyczar、操作系统的加密API,都只是计算安全的。有什么不同?信息论中安全的算法肯定是安全的,绝对的,其他算法理论上可能被穷举算力破解。我们仍然愉快地使用它们,因为世界上所有的计算机加起来在宇宙时代是不可能破解的,至少目前是这样。这就是我们在文章中所说的“不安全”的意思。除非有聪明人破解了算法本身——用比现在可实现的计算能力小得多的计算能力。它也是每个密码学家梦想的圣杯:破解AES本身,破解RSA本身,等等。所以现在我们来看看更底层的东西:随机数生成器,你坚持“真随机”而不是“伪随机”。但是不久之后,您的真随机数就会被输入到您非常鄙视的伪随机算法中!事实是,如果我们的散列算法被破坏,或者块密码算法被破坏,您获得这些“哲学上不安全”的随机数甚至都没有关系,因为无论如何您都没有安全的方法来应用它们。因此,仅使用计算安全的算法即可为您提供计算安全的随机数,换句话说,使用/dev/urandom。Linux随机数生成器的结构是错误的看法你对内核随机数生成器的理解很可能是这样的:image:神话般的内核随机数生成器结构“真正的随机性”,虽然可能有点瑕疵,进入操作系统,它的熵立即被添加到内部熵计数器。然后在“偏置”和“漂白”之后它进入内核的熵池,然后/dev/random和/dev/urandom从中生成随机数。“真正的”随机数生成器/dev/random直接从池中挑选随机数,吐出数字并在熵计数器指示足够大的数字大小时递减熵计数。如果不是,它会阻塞程序,直到有足够的熵进入系统。这里的重要部分是/dev/random几乎只是吐出进入系统的随机性,没有失真,仅经过必要的“漂白”。对于/dev/urandom,情况是一样的。除了当没有足够的熵时它不会阻塞,并从永远在线的伪随机数生成器(当然是加密安全的CSPRNG)中吐出“低质量”随机数。这个CSPRNG只会被“真正的随机数”播种一次(或几次,没关系),但你不能特别相信它。在这种对随机数生成的理解下,很多人会认为在Linux下尽量避免/dev/urandom似乎是合理的。因为要么你有足够的熵,你将相当于使用/dev/random。否则,您将从几乎没有高熵输入的CSPRNG中获得低质量的随机数。看起来很邪恶,不是吗?不幸的是,这种看法是完全错误的。其实随机数生成器的结构更像下面这样。BetterSimplifybeforeLinux4.8图片:Linux4.8之前内核随机数生成器的实际结构你看到区别了吗?CSPRNG不使用随机数生成器运行,它会在需要输出但熵不够时填充/dev/urandom。CSPRNG是整个随机数生成过程的内部组件之一。从来没有直接从池中输出纯随机性的/dev/random。每个随机源的输入都在CSPRNG中完全混合和散列,所有这些都是在实际变成随机数之前由/dev/urandom或/dev/random吐出的。另一个重要的区别是这里没有任何东西的熵计数器,只有估计值。一个源给你的熵量并不是一个你可以直接得到的明确定义的数字。你必须估计它。请注意,/dev/random最重要??的特征-仅提供熵允许的尽可能多的随机性-如果您过于乐观地估计它就会丢失。不幸的是,估计熵的量很困难。这是一个粗略的简化。实际上不是一个,而是三个熵池。一个主池,另一个用于/dev/random,一个用于/dev/urandom,后两者依赖于从主池中获取熵。三个池子都有自己的熵计数器,但二级池(最后两个)的计数器基本上都在0左右,“新鲜”的熵总是在需要时从主池流出。同时还有大量的混合和回流进入系统。整个过程对于这篇文档来说太复杂了,所以我们跳过它。Linux内核仅使用事件的到达时间来估计熵的数量。根据该模型,它使用多项式插值来估计实际到达时间的“意外”程度。这种多项式插值方法是否是预测熵的好方法本身就是一个问题。硬件情况是否会以某种方式影响到达时间也值得怀疑。所有硬件的采样率也是一个问题,因为这个基本上直接决定了随机数到达时间的粒度。至少目前来说,kernel的熵估计还是不错的。这也意味着它是保守的。有些人会具体讨论它有多好,这超出了我的理解范围。即便如此,如果你坚持不想吐出熵不够的随机数,看到这里你可能还是有点紧张。我睡得很香,因为我不关心熵估计或任何事情。需要明确的是:/dev/random和/dev/urandom都由同一个CSPRNG提供。它们只有在耗尽各自的熵池时才会表现不同(根据一些估计):/dev/random块,/dev/urandom没有。Linux4.8以上图片:Linux4.8以上内核随机数生成器的实际结构在Linux4.8中,/dev/random和/dev/urandom的等效性被放弃了。现在/dev/urandom的输出不是来自熵池,而是直接来自CSPRNG。我们很快就会明白为什么这不是安全问题。(参见:“CSPRNGOK”。)阻塞有什么问题?您是否曾经不得不等待/dev/random吐出随机数?就像在虚拟机中生成PGP密钥一样?或者访问正在生成会话密钥的网站?这些都是问题。阻塞本质上会降低可用性。换句话说,您的系统不执行您告诉它执行的操作。不用说,这很糟糕。如果它不起作用,你为什么要建造它?我曾在工厂自动化中从事与安全相关的系统工作。猜猜安全系统失效的主要原因是什么?操作问题。就这么简单。很多安全措施的过程让工人们很恼火。比如太长,或者太不方便。要知道,人们非常善于寻找“解决”问题的捷径。但还有一个更深层次的问题:人们不喜欢被打扰。他们会找到绕过它的方法,将奇怪的东西放在一起只是因为它有效。一般人对密码学或任何乱七八糟的东西一无所知,至少普通人是这样。为什么不禁止调用random()?为什么不直接在论坛上找人告诉你写花哨的ioctl来增加熵计数器呢?为什么不直接关闭SSL加密呢?最后,如果某些东西太难使用,您的用户将被迫开始做一些降低系统安全性的事情——您甚至不知道他们将要做什么。很容易忽视诸如可用性之类的重要性。毕竟安全很重要,对吧?所以不能用、难用、不方便都是牺牲安全的次要因素?这种二元思维是错误的。阻塞不一定安全。正如我们所见,/dev/urandom直接从CSPRNG为您提供同样好的随机数。用着不是很好吗!CSPRNG没问题现在事情听起来很凄凉。如果连高质量的/dev/random都来自CSPRNG,我们怎么敢将它用于高安全性要求?事实上,“看起来随机”是大多数现有密码学基础组件的基本要求。如果您查看密码散列的输出,它必须与随机字符串无法区分,密码学家才能识别该算法。如果生成分组密码,则其输出(不知道密钥)也必须与随机数据无法区分。如果有人能比蛮力更有效地破解加密,比如说它利用了一些CSPRNG伪随机性弱点,那么它又是同样的陈词滥调:全部报废,更不用说后者了。区块加密、哈希,一切都是基于一定的数学算法,比如CSPRNG。所以不要害怕,到头来都是一样的。当熵池几乎为空时呢?没有效果。加密算法基于黑客无法预测输出的事实,只要一开始就有足够的随机性(熵)。“足够”的下限可以是256位,仅此而已。由于我们一直很随意地使用“熵”这个概念,所以我用“比特”来量化随机性,希望读者不要太在意细节。正如我们之前讨论的,内核的随机数生成器甚至不知道系统中有多少熵。只有一个估计。没有人知道这一估计有多准确。Reseed但如果熵如此不重要,为什么新的熵不断被输入随机数生成器?djb提到太多的熵甚至会适得其反。首先,它通常不会。如果您有很多随机性,请使用它!但是还有其他原因导致随机数生成器需要时不时地重新设定种子:想象一下如果黑客获得了随机数生成器的所有内部状态。这是最坏的情况,基本上你的一切都暴露在黑客面前。你已经凉了,因为黑客可以计算出所有未来输出的随机数。但是,如果不断向系统添加新的熵,内部状态将再次变得随机。所以随机数发生器被设计成具有一定的“自愈”能力。但这是给内部状态引入了新的熵,与阻塞输出无关。random和urandom的手册页这两个手册页在吓唬程序员方面做得很好:从/dev/urandom读取不会阻塞,因为它需要更多的熵。这样做的结果是,如果熵池中没有足够的熵,根据驱动程序使用的算法,理论上返回值可能会被密码学破解。启动这样一个程序并没有出现在任何已发表的文献中,但它在理论上是可能的。如果您的应用程序担心这种情况,您应该使用/dev/random。实际上已经有/dev/random和/dev/urandom的Linux内核手册页的更新版本。不幸的是,任何网络搜索都在结果的顶部出现了我仍然旧的、有问题的版本。此外,许多Linux发行版仍在发布旧的手册页。不幸的是,该部分需要在这篇文章中保留更长的时间。我期待删除此部分!没有“公共文献”的描述,但NSA的小卖部必须出售这种威胁,对吧?如果你真的非常担心(你应该担心),使用/dev/random并且所有问题都会消失?然而,事实是某些情报机构可能有这种方法,或者某些邪恶的黑客组织可能已经找到了方法。但假设这种威胁一定存在是不合理的。而且就算你想让自己省心,我也给你泼冷水:AES、SHA-3或者其他常见的加密算法都没有“公开文档描述”的手段。你不用这些加密算法吗?这显然是荒谬的。我们回到手册页中说:“使用/dev/random”。我们已经知道/dev/urandom虽然不阻塞,但是它的随机数和/dev/random来自同一个CSPRNG。如果您真的需要信息理论上安全的随机数(而您不需要,相信我),这可能是您需要等待足够的熵进入CSPRNG的原因。而且您也不能使用/dev/random。手册页是有毒的,仅此而已。但至少它可以自我弥补一点:如果您不确定是使用/dev/random还是/dev/urandom,您应该使用后者。通常,除了长期GPG/SSL/SSH密钥外,您应该始终使用/dev/urandom。该手册页的当前更新版本明确表示:/dev/random接口被认为是旧接口,并且/dev/urandom在所有用例中都足够了,除了在启动早期需要随机性的应用程序;对于这些应用程序,必须改用getrandom(2),因为它将阻塞直到熵池初始化完成。好的。我不认为这是必要的,但如果你真的想使用/dev/random来生成“长寿命密钥”,那是没有办法阻止的!您可能需要等待几秒钟或敲击几次键盘才能使熵增加,但这没关系。但是请不要仅仅因为“你想更安全”就让邮件服务器挂半天。正道这篇文章的观点在网络上显然是“小众”。但是问一个真正的密码学家,你很难找到一个同意阻止/dev/random的人。例如,让我们看一下DanielBernstein(著名的djb):我们密码学家不对这种随机迷信负责。仔细想想,写/dev/random手册页的人似乎同时相信:(1)我们不知道如何使用256位长的/dev/random输出来生成随机密钥流(这是我们需要的/dev/urandom),但同时(2)我们知道如何使用单个密钥加密消息(这就是SSL、PGP等所做的)这对密码学家来说甚至都不好笑或者ThomasPornin,我在stackexchange上见过的最有帮助的人之一:简而言之,是的。展开,答案还是一样。/dev/urandom生成的数据可以说与真正的随机性完全没有区别,至少在目前的技术水平上是这样。除非您使用极其罕见的“信息论安全”加密算法,否则使用比/dev/urandom“更好”的随机性是没有意义的。这当然不是你的情况,否则你会这么说。urandom手册页有点误导,或者完全错误——尤其是当它说/dev/urandom“熵耗尽”和“/dev/random更好”时;或者ThomasPtacek的观点,他不设计密码算法或密码系统,但他是一家知名安全咨询公司的创始人,这家公司负责许多渗透和破解不良密码算法的测试:使用urandom。使用随机数。使用随机数。使用随机数。使用随机数。/dev/urandom的问题是双重的:在Linux上,与FreeBSD不同,/dev/urandom从不阻塞。还记得安全性如何取决于某些初始决定的随机性吗?种子?Linux的/dev/urandom会很高兴地为你吐出一些不太随机的随机数,在内核甚至有机会收集熵之前。这是什么时候的事?当您的系统刚刚启动时。FreeBSD的行为更正确:/dev/random与/dev/urandom相同,并且/dev/random在系统启动时阻塞,直到有足够的熵,然后它们都不再阻塞。同时,Linux实现了一个新的系统调用syscall,OpenBSD最先引入的叫getentrypy(2),在Linux下这个叫getrandom(2)。这个系统调用具有上述的正确行为:阻塞直到有足够的熵,然后再也不会阻塞。当然这是系统调用,不是字节设备(LCTT译注:不在/dev/下),所以在shell或者其他脚本语言中不是那么容易搞定的。这个系统调用从Linux3.17开始存在。在Linux上,这实际上不是一个问题,因为Linux发行版在启动期间将一些随机性保存到种子文件中(这发生在已经有一些熵之后,因为启动过程不会在按下电源后立即开始运行)以便系统可以在下次启动时读取它。所以每次系统启动的时候,都会从上次的session中带一点随机性。显然这不如在关闭脚本中写入一些随机种子好,因为那显然有更多的熵可以操纵。但这样做的明显好处是它不必关心系统是否正确关闭,例如,你的系统可能会崩溃。而且这个方法在你真正启动系统的时候并不能帮你随机化,不过好在Linux系统安装器一般都会保存一个种子文件,所以基本上问题不大。虚拟机是另一层关注点。因为用户喜欢克隆它们,或者恢复到以前的某个状态。在这种情况下,torrent文件将无济于事。但是解决方案仍然与使用/dev/random无关,相反,您应该正确地重新播种每个克隆或恢复的图像。不要问是不是太长了,用/dev/urandom就行了!