为什么 OpenSSL 不能直接使用 /dev/random?

信息安全 openssl 密钥生成 随机的
2021-08-27 08:06:40

我在任何地方都找不到原因的答案,甚至维基页面也没有解释。这似乎是使用 PRNG 播种另一个 PRNG。第一个(/dev/random)本身可能由 PRNG 播种。

我可以“理解”为什么该-rand选项不能直接用于生成密钥,但我看不到主要来源的原因。许多人说/dev/random没有产生足够的输出,当它的熵由守护进程提供时这是错误的。

他们认为/dev/random不是优质来源吗?

4个回答

从设备读取字节可能很麻烦(您必须考虑系统调用的特殊性,例如中断的系统调用),并且如果读取许多小块(系统调用具有不可忽略的开销) ,则可能效率低下。自定义软件 PRNG,以来自 的字节为种子/dev/urandom,可以更好地控制性能。

(此外,这里也可能有一点NIH综合症。)

如果您要问为什么openssl randRAND_bytes()不简单地反刍/dev/randomor /dev/urandom,那是因为它们的功能只是充当 PRNG,而它们正是这样做的:

rand命令在为随机数生成器播种一次后输出 num 个伪随机字节。

正确编译和运行的 OpenSSL 将从/dev/urandom可用的地方读取 32 个字节(除非指定了硬件引擎),并使用(部分或全部)它来播种 PRNG。您可以通过以下方式确认是否在 *nix 系统上读取randomurandom读取:strace

$ strace -xe trace=file,read,write,close openssl rand 10
[...] 
open("/dev/urandom", O_RDONLY|O_NOCTTY|O_NONBLOCK) = 3
read(3, "\x26\x20\xfc\x61\x59\x81\xc9\x4d\xe5\x27\xab\xc2\x72\xb1"..., 32) = 32
close(3)                                = 0

至于为什么会这样,作为一个软件库,OpenSSL 不能直接生成真正的随机数选择是使用具有良好随机种子的加密安全 PRNG(即操作系统从有效的随机硬件事件中收集数据);或使用真正的硬件 RNG。wiki的硬件部分解释了如何执行后者,包括如何使用“引擎”功能访问片上(英特尔的 RDRAND)RNG。Thomas Pornin 在这里的出色回答解释了为什么(正确播种、加密安全)PRNG 对于大多数目的来说已经足够了:Evaluating the entropy aggregation in a PRNG

如果您想负责以可配置的方式向 OpenSSL 提供随机数据,那么使用EGD 样式的套接字可能是最简单的选择。如果您想直接通过 OpenSSL API 提供随机数,引擎是“最佳”选项(尽管您可以作弊,例如这里

我相信 OpenSSL 使用它自己的 PRNG 算法,除非你启用了 FIPS。

OpenSSL 在可用时绝对使用/dev/random:摘自FAQ中的一个答案

默认情况下,所有 OpenSSL 版本都尝试使用 /dev/urandom;从版本 0.9.7 开始,如果 /dev/urandom 不可用,OpenSSL 也会尝试 /dev/random。

以及您引用的 wiki 页面:

初始化

OpenSSL 将尝试通过调用 RAND_poll 在实例化时自动为随机数生成器播种。RAND_poll 使用系统特定的熵源作为随机数生成器的种子,在类 UNIX 操作系统上为 /dev/urandom,在 Windows 上是 CryptGenRandom 和其他熵源的组合。


您还询问这是否是“从 PRNG 中播种 PRNG”,这取决于您定义 PRNG 的严格程度。PRNG的一个定义属性是它是确定性的,PRNG 有一个“秘密”起点(由种子定义),此后它是可预测的(如果您知道该秘密和当前迭代,或者通过扩展内部状态) . 虽然普通的 Linuxrandom显然不是真正的“不可观察”或量子 RNG,但它也不是一个普通的 PRNG,因为它的输出很快就会与初始播种会导致的确定性值不同。抛开猜想,我认为当代术语是“带输入的 PRNG ”,见此PDF 论文,所以合格“

从 0.9.7 开始,OpenSSL 默认(DEVRANDOM定义)按顺序使用:/dev/urandom, /dev/random, /dev/srandom(具有 10ms 非阻塞读取)并建议:

/* Use a random entropy pool device. Linux, FreeBSD and OpenBSD
 * have this. Use /dev/urandom if you can as /dev/random may block
 * if it runs out of random entries.  */

(OpenSSL-1.0.1e crypt/rand/rand_unix.c)

" " 的内容random应该谨慎使用,因为它带有一些隐含的质量并且是有速率限制的。虽然urandom不会阻塞,但暗示其质量会随着使用而降低。(至少在 Linux 上,urandom在其他平台上是不同的。)

最后,一些古老的历史...... OpenSSL 源自SSLeay(OpenSSL 诞生于 v0.9.0),而 SSLeay(1995 年 4 月发布的 0.1)早于常用的操作系统管理 (P)RNG。Linux 是第一个在 1994 年获得randomv1.3.30设备的设备,随后是1995 年的FreeBSDRAND_bytes() 2.2。PRNG 是从这个时候开始的,所以在文档中看到很有趣(doc/rand.doc从 0.5.1b 开始):

First up I will state the things I believe I need for a good RNG.
1) A good hashing algorithm to mix things up and to convert the RNG 'state'
   to random numbers.
2) An initial source of random 'state'.
3) The state should be very large. [...snip...]

随后是进一步的讨论和有先见之明的总结:

So of the points raised, only 2 is not addressed, but sources of random data 
will always be a problem.

SSLeay/dev/random在 0.5.3 版本中首次获得支持(可能是 1996 年初,更改日志有点模糊)。在此之前 PID、UID、时间戳和 AFAICT,堆栈上的任何内容都必须足够。PRNG 总是如此RAND_bytes(),因为这是一种快速且便携的解决方案。它使用它可以清除的任何东西来播种 PRNG 以提供有用数量的随机数据,包括操作系统提供的随机性。当 PRNG 有特定于硬件的替代方案时,加密引擎可以提供此功能。

关于-rand我假设你的意思是与“”之类的东西一起使用时的行为openssl genrsa 2028它可能只是以这种方式运行,因为需要适度但不确定的随机数据量(随机生成数字并测试素数,需要一对),而替代方案从未被认为有用(大约 10-40 kiB 的真正随机数据,作为有根据的猜测)。

一般来说,不直接使用随机源的原因包括:

  • 您可能不需要与需要比特一样多的熵。在这种情况下,您可以使用随机位来播种伪随机数生成器。当熵稀缺时,这是最重要的(随着时间的推移,随着专用硬件熵源变得越来越普遍,这种约束变得越来越不相关)。
  • 您不想相信随机源是随机的。 /dev/urandom特别是不值得信赖,因为在保证它不会在低熵条件下阻塞时,它不能保证输出实际上是随机的。联机帮助页man 4 urandom对此有更多信息,包括对某些政府机构可能或可能不会基于这种情况进行的攻击的隐晦暗示。可以对这些数据进行适用性测试,或者可以以某种方式对其进行变异以集中熵,或者可以组合不同的来源。
  • 您希望保证使用了特定的生成随机数的方法。这允许您遵守任何强制执行此操作的标准,而无需依赖 Linux 内核开发人员为您执行此操作。

还要考虑有效密钥大小的概念(在给定用它加密的某些已知项目的情况下,一些用于破解密钥的最佳算法的迭代次数的以 2 为底的对数)。显然,消耗比有效密钥大小更多的熵是没有意义的,这样做只会耗尽熵池。

除了猎鹰的出色回答:

[colin@localhost ~]$ time dd if=/dev/random of=/dev/null iflag=fullblock count=10 bs=32
10+0 records in
10+0 records out
320 bytes (320 B) copied, 32.0067 s, 0.0 kB/s
0.00user 0.00system 0:32.00elapsed 0%CPU (0avgtext+0avgdata 3104maxresident)k
0inputs+0outputs (0major+249minor)pagefaults 0swaps

[colin@localhost ~]$ time dd if=/dev/urandom of=/dev/null iflag=fullblock count=10 bs=32
10+0 records in
10+0 records out
320 bytes (320 B) copied, 0.000289421 s, 1.1 MB/s
0.00user 0.00system 0:00.00elapsed 0%CPU (0avgtext+0avgdata 3072maxresident)k
0inputs+0outputs (0major+247minor)pagefaults 0swaps

/dev/random 可能非常随机,但它非常非常慢!