Ruby的OpenSSL :: Random的种子是否充足?

我对Ruby知之甚少,所以请原谅我,如果答案是显而易见的。 我在http://www.ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/rdoc/SecureRandom.html注意到Ruby在调用random_bytes时使用pid和当前时间来播种OpenSSL :: Random是。 除非发生其他事情,否则这不是Netscape在90年代中期初始SSL实施中使用的种子吗? http://en.wikipedia.org/wiki/Random_number_generator_attack#Prominent_examples_of_random_number_generator_security_issues

当然,Ruby还没有复活一个18岁的小虫。 我在这里想念的是什么?

编辑:这是random_bytes的来源。 请注意第一次检查是否使用OpenSSL编译ruby,在这种情况下,它会使用pid和当前时间对其进行种子处理。

def self.random_bytes(n=nil) n = n ? n.to_int : 16 if defined? OpenSSL::Random @pid = 0 if !defined?(@pid) pid = $$ if @pid != pid now = Time.now ary = [now.to_i, now.nsec, @pid, pid] OpenSSL::Random.seed(ary.to_s) @pid = pid end return OpenSSL::Random.random_bytes(n) end if !defined?(@has_urandom) || @has_urandom flags = File::RDONLY flags |= File::NONBLOCK if defined? File::NONBLOCK flags |= File::NOCTTY if defined? File::NOCTTY begin File.open("/dev/urandom", flags) {|f| unless f.stat.chardev? raise Errno::ENOENT end @has_urandom = true ret = f.readpartial(n) if ret.length != n raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes" end return ret } rescue Errno::ENOENT @has_urandom = false end end if !defined?(@has_win32) begin require 'Win32API' crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L') @crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L') hProvStr = " " * 4 prov_rsa_full = 1 crypt_verifycontext = 0xF0000000 if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0 raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}" end @hProv, = hProvStr.unpack('L') @has_win32 = true rescue LoadError @has_win32 = false end end if @has_win32 bytes = " ".force_encoding("ASCII-8BIT") * n if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0 raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}" end return bytes end raise NotImplementedError, "No random device" end 

这取决于使用RNG的Ruby的配置:

安全随机数生成器接口。

该库是安全随机数生成器的接口,适用于在HTTP cookie等中生成会话密钥。

它支持以下安全随机数生成器。

  • OpenSSL的

  • /dev/urandom

  • Win32的

上述所有三个通常被认为是安全的。 但是,如果它实际上是安全的,它取决于SecureRandom类的实现。 了解这一点的唯一方法是对实施进行广泛的研究。

在查看问题中的代码时,很明显Ruby直接使用OpenSSL生成的字节,在另外播种PID之后:

每当添加种子数据时,它将按如下方式插入“状态”。

输入被斩波为20字节(或最后一个块的更少)。 这些块中的每一个都通过哈希函数运行如下:传递给哈希函数的数据是当前的’md’,与’state’相同的字节数(由递增的循环索引确定的位置)作为当前’block’,新的密钥数据’block’和’count’(每次使用后递增)。 其结果保存在’md’中,并且在用作哈希函数输入的相同位置处进入’状态’。 我相信这个系统解决了点1(散列函数;当前SHA-1),3(’状态’),4(通过’md’),5(通过使用散列函数和xor)。

SecureRandom中使用的种子禁止在PID被回收时发生的可预测的随机数。 如果没有SecureRandom的修复,OpenSSL的随机数生成器将在具有相同PID的不同进程中生成完全相同的值。

#4579概述了如何发生这种情况,OpenSSL邮件列表上的相应条目告诉我们或多或少会在客户端代码中处理这个问题。 这就是为什么在Ruby中选择这个种子来防止安全威胁的原因。 如果不相信,在此修复之前运行脚本 Eric Wong附加在Ruby版本上,看看这是什么。

加上owlstead的解释,此时播种OpenSSL的RNG不会危及安全性,因为未初始化的随机生成器将始终首先调用RAND_poll ,这将收集足够的熵,无论先前是否已经播种/添加了值。

但是,由于SecureRandom中的种子值明显可以预测,我们不应该假设他们添加任何熵。 OpenSSL的内部行为可能会在某些时候发生变化,如果已经播种的值被认为包含足够的熵,它可能会跳过初始熵收集。

因此,我打开了#6928 ,它会选择一种更具防御性的方法,即假设没有熵来添加到熵池中以区分不同的过程 – 这将迫使OpenSSL在所有情况下可靠地收集足够的熵。

总之,值(PID和时间)的选择是合理的,它甚至增加了整体安全性(通过防止“再循环PID攻击”)而不是减少它。

我的一位同事对此进行了调查,发现种子的选择是作为对这个错误的回应而引入的:

http://bugs.ruby-lang.org/issues/4579

幸运的是,OpenSSL自身具有来自/ dev / urandom(如果可用)的256位熵,或者egd(’熵收集守护进程’ – / dev / urandom的前身),具体取决于它的编译方式。 播种在第一次调用RAND_status()或RAND_bytes()时自动发生,如果显式调用RAND_seed(),则不会抑制播种。 感谢OpenSSL人员做出这一决定。 这是特定OpenSSL代码的链接:

http://cvs.openssl.org/dir?d=openssl/crypto/rand

有趣的文件是md_rand.c,rand_lib.c和rand_unix.c。