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/