I know very little about Ruby, so please forgive me if the answer to this is obvious. I noticed at http://www.ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/rdoc/SecureRandom.html that Ruby uses the pid and the current time to seed OpenSSL::Random when a call to random_bytes is made. Unless something else happens under the covers, isn't this pretty much the seed that Netscape used in their initial SSL implementation in the mid 90s? http://en.wikipedia.org/wiki/Random_number_generator_attack#Prominent_examples_of_random_number_generator_security_issues
Surely Ruby hasn't revived an 18 year old bug. What am I missing here?
Edit: Here's the source for random_bytes. Notice the first check to see if ruby was compiled with OpenSSL, in which case it seeds it with the pid and current time.
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
The seed being used in SecureRandom
prohibits predictable random numbers that occur whenever PIDs get recycled. Without the fix in SecureRandom
, OpenSSL's random number generator will produce the exact same values in different processes that possess the same PID.
#4579 outlines how this can happen, and corresponding entry on OpenSSL's mailing list tells us more or less that this has to be dealt with in client code. This is why this seed was chosen in Ruby to prevent a security threat. If not convinced, run the script Eric Wong attached on a Ruby version prior to this fix to see what this was all about.
Adding to owlstead's explanation, seeding OpenSSL's RNG at this point doesn't compromise security, because an uninitialized random generator will always call RAND_poll
first, which will gather enough entropy regardless of whether values have been previously seeded/added or not.
However, since the seed values in SecureRandom
are clearly predictable, we should not assume that they add any entropy. OpenSSL's internal behavior could change at some point and it could skip the initial entropy gathering if the values already seeded are considered to contain enough entropy.
I therefore opened #6928, which would choose a more defensive approach of assuming no entropy for the values added to the entropy pool that distinguish different processes - this would force OpenSSL to reliably collect enough entropy in all cases.
In conclusion, the choice of values (PID and time) was a sensible one, it even adds to the overall security (by preventing the "recycled PID attack") instead of diminishing it.
It depends on the configuration of Ruby which RNG is used:
Secure random number generator interface.
This library is an interface for secure random number generator which is suitable for generating session key in HTTP cookies, etc.
It supports following secure random number generators.
openssl
/dev/urandom
Win32
All three of the above are generally considered secure. However, it depends on the implementation of the SecureRandom
class if it actually is secure. The only way of knowing this is an extensive research into the implementations.
Looking at the code in the question it is clear that Ruby directly uses bytes generated by OpenSSL, after additionally seeding the PID:
Whenever seed data is added, it is inserted into the 'state' as follows.
The input is chopped up into units of 20 bytes (or less for the last block). Each of these blocks is run through the hash function as follows: The data passed to the hash function is the current 'md', the same number of bytes from the 'state' (the location determined by in incremented looping index) as the current 'block', the new key data 'block', and 'count' (which is incremented after each use). The result of this is kept in 'md' and also xored into the 'state' at the same locations that were used as input into the hash function. I believe this system addresses points 1 (hash function; currently SHA-1), 3 (the 'state'), 4 (via the 'md'), 5 (by the use of a hash function and xor).
A colleague of mine investigated this, and found that the choice of seed was introduced as a response to this bug:
http://bugs.ruby-lang.org/issues/4579
Fortunately, OpenSSL seeds itself with 256 bits of entropy from /dev/urandom (if available), or the egd ('entropy gathering daemon' -- a precursor to /dev/urandom) depending on the how it was compiled. The seeding happens automatically the first time RAND_status() or RAND_bytes() is called, and is not suppressed if RAND_seed() is called explicitly. Kudos to the OpenSSL folks for this decision. Here's a link to the specific OpenSSL code:
http://cvs.openssl.org/dir?d=openssl/crypto/rand
The interesting files are md_rand.c, rand_lib.c, and rand_unix.c.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With