8

我对 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 岁的 bug 复活。我在这里想念什么?

编辑:这是 random_bytes 的来源。请注意第一次检查 ruby​​ 是否使用 OpenSSL 编译,在这种情况下,它会使用 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
4

3 回答 3

6

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

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

添加到 owlstead 的解释中,此时播种 OpenSSL 的 RNG 并不会损害安全性,因为未初始化的随机生成器将始终RAND_poll首先调用,这将收集足够的熵,而不管先前是否播种/添加了值。

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

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

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

于 2012-08-27T03:55:52.713 回答
3

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

安全的随机数生成器接口。

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

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

  • openssl

  • /dev/urandom

  • Win32

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

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

每当添加种子数据时,都会将其插入到“状态”中,如下所示。

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

于 2012-08-25T15:31:54.303 回答
2

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

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

幸运的是,OpenSSL 使用来自 /dev/urandom(如果可用)或 egd(“熵收集守护进程”——/dev/urandom 的前身)的 256 位熵为自己播种,具体取决于它的编译方式。播种在第一次调用 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。

于 2012-08-25T18:08:27.433 回答