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.



git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@57384 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
shyouhei 2017-01-20 08:00:00 +00:00
Родитель e95eb9584a
Коммит abae70d6ed
3 изменённых файлов: 57 добавлений и 19 удалений

Просмотреть файл

@ -1,9 +1,5 @@
# -*- coding: us-ascii -*-
# frozen_string_literal: true
begin
require 'openssl'
rescue LoadError
end
# == Secure random number generator interface.
#
@ -48,8 +44,47 @@ end
#
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
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
ret
end
end
class << self
alias bytes gen_random
end
end
module Random::Formatter

Просмотреть файл

@ -603,11 +603,18 @@ random_seed(void)
}
/*
* 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.
*
* Random.raw_seed(8) #=> "\x78\x41\xBA\xAF\x7D\xEA\xD8\xEA"
* 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.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)
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);

Просмотреть файл

@ -550,9 +550,9 @@ END
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