[前][次][番号順一覧][スレッド一覧]

ruby-changes:45311

From: shyouhei <ko1@a...>
Date: Fri, 20 Jan 2017 17:00:04 +0900 (JST)
Subject: [ruby-changes:45311] shyouhei:r57384 (trunk): SecureRandom should try /dev/urandom first [Bug #9569]

shyouhei	2017-01-20 17:00:00 +0900 (Fri, 20 Jan 2017)

  New Revision: 57384

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=57384

  Log:
    SecureRandom should try /dev/urandom first [Bug #9569]
    
    * random.c (InitVM_Random): rename Random.raw_seed to
      Random.urandom.  A quick search seems there are no practical use
      of this method than securerandom.rb so I think it's OK to rename
      but if there are users of it, this hunk is subject to revert.
    
    * test/ruby/test_rand.rb (TestRand#test_urandom): test for it.
    
    * lib/securerandom.rb (SecureRandom.gen_random): Prefer OS-
      provided CSPRNG if available. Otherwise falls back to OpenSSL.
      Current preference is:
    
      1. CSPRNG routine that the OS has; one of
         - getrandom(2),
         - arc4random(3), or
         - CryptGenRandom()
      2. /dev/urandom device
      3. OpenSSL's RAND_bytes(3)
    
      If none of above random number generators are available, you
      cannot use this module.  An exception is raised that case.

  Modified files:
    trunk/lib/securerandom.rb
    trunk/random.c
    trunk/test/ruby/test_rand.rb
Index: random.c
===================================================================
--- random.c	(revision 57383)
+++ random.c	(revision 57384)
@@ -603,11 +603,18 @@ random_seed(void) https://github.com/ruby/ruby/blob/trunk/random.c#L603
 }
 
 /*
- * call-seq: Random.raw_seed(size) -> string
+ * call-seq: Random.urandom(size) -> string
  *
- * Returns a raw seed string, using platform providing features.
+ * Returns a string, using platform providing features.
+ * Returned value expected to be a cryptographically secure
+ * pseudo-random number in binary form.
+ *
+ * In 2017, Linux manpage random(7) writes that "no cryptographic
+ * primitive available today can hope to promise more than 256 bits of
+ * security".  So it might be questionable to pass size > 32 to this
+ * method.
  *
- *   Random.raw_seed(8)  #=> "\x78\x41\xBA\xAF\x7D\xEA\xD8\xEA"
+ *   Random.urandom(8)  #=> "\x78\x41\xBA\xAF\x7D\xEA\xD8\xEA"
  */
 static VALUE
 random_raw_seed(VALUE self, VALUE size)
@@ -1616,7 +1623,7 @@ InitVM_Random(void) https://github.com/ruby/ruby/blob/trunk/random.c#L1623
     rb_define_singleton_method(rb_cRandom, "srand", rb_f_srand, -1);
     rb_define_singleton_method(rb_cRandom, "rand", random_s_rand, -1);
     rb_define_singleton_method(rb_cRandom, "new_seed", random_seed, 0);
-    rb_define_singleton_method(rb_cRandom, "raw_seed", random_raw_seed, 1);
+    rb_define_singleton_method(rb_cRandom, "urandom", random_raw_seed, 1);
     rb_define_private_method(CLASS_OF(rb_cRandom), "state", random_s_state, 0);
     rb_define_private_method(CLASS_OF(rb_cRandom), "left", random_s_left, 0);
 
Index: lib/securerandom.rb
===================================================================
--- lib/securerandom.rb	(revision 57383)
+++ lib/securerandom.rb	(revision 57384)
@@ -1,9 +1,5 @@ https://github.com/ruby/ruby/blob/trunk/lib/securerandom.rb#L1
 # -*- coding: us-ascii -*-
 # frozen_string_literal: true
-begin
-  require 'openssl'
-rescue LoadError
-end
 
 # == Secure random number generator interface.
 #
@@ -48,8 +44,47 @@ end https://github.com/ruby/ruby/blob/trunk/lib/securerandom.rb#L44
 #
 
 module SecureRandom
-  if defined?(OpenSSL::Random) && /mswin|mingw/ !~ RUBY_PLATFORM
-    def self.gen_random(n)
+  @rng_chooser = Mutex.new # :nodoc:
+
+  class << self
+    def bytes(n)
+      return gen_random(n)
+    end
+
+    def gen_random(n)
+      ret = Random.urandom(n)
+      if ret.nil?
+        begin
+          require 'openssl'
+        rescue NoMethodError
+          raise NotImplementedError, "No random device"
+        else
+          @rng_chooser.synchronize do
+            class << self
+              remove_method :gen_random
+              alias gen_random gen_random_openssl
+            end
+          end
+          return gen_random(n)
+        end
+      elsif ret.length != n
+        raise NotImplementedError, \
+              "Unexpected partial read from random device: " \
+              "only #{ret.length} for #{n} bytes"
+      else
+        @rng_chooser.synchronize do
+          class << self
+            remove_method :gen_random
+            alias gen_random gen_random_urandom
+          end
+        end
+        return gen_random(n)
+      end
+    end
+
+    private
+
+    def gen_random_openssl(n)
       @pid = 0 unless defined?(@pid)
       pid = $$
       unless @pid == pid
@@ -63,9 +98,9 @@ module SecureRandom https://github.com/ruby/ruby/blob/trunk/lib/securerandom.rb#L98
       end
       return OpenSSL::Random.random_bytes(n)
     end
-  else
-    def self.gen_random(n)
-      ret = Random.raw_seed(n)
+
+    def gen_random_urandom(n)
+      ret = Random.urandom(n)
       unless ret
         raise NotImplementedError, "No random device"
       end
@@ -75,10 +110,6 @@ module SecureRandom https://github.com/ruby/ruby/blob/trunk/lib/securerandom.rb#L110
       ret
     end
   end
-
-  class << self
-    alias bytes gen_random
-  end
 end
 
 module Random::Formatter
Index: test/ruby/test_rand.rb
===================================================================
--- test/ruby/test_rand.rb	(revision 57383)
+++ test/ruby/test_rand.rb	(revision 57384)
@@ -550,9 +550,9 @@ END https://github.com/ruby/ruby/blob/trunk/test/ruby/test_rand.rb#L550
     End
   end
 
-  def test_raw_seed
+  def test_urandom
     [0, 1, 100].each do |size|
-      v = Random.raw_seed(size)
+      v = Random.urandom(size)
       assert_kind_of(String, v)
       assert_equal(size, v.bytesize)
     end

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]