This post originated from an RSS feed registered with Ruby Buzz
by Jan Lelis.
Original Post: Ruby and Random
Feed Title: rbJ*_*L.net
Feed URL: http://feeds.feedburner.com/rbJL
Feed Description: Hi, I am a fan of Ruby and like to explore it and the world around ;).
So I started this blog, where I am publishing code snippets, tutorials for beginners as well as general thoughts about Ruby, the web or programming in general.
Wrongly used randomness can be the source of hard-to-detect bugs and security holes. This is relevant every time you use randomness, for example, when implementing an existing protocol/interface that requires random values or generating tokens for your next raffle. This article describes when to use which of Ruby’s randomness methods.
Don’t use or rely on srand, because of its global state. Generally use SecureRandom, which is suitable for cryptographic purposes! If you need reproducible randomness, use your own Random object!
Basic Ways to use Randomness in Ruby
Generating random values in Ruby is convenient:
Create Numbers
rand 10 # integer between 0..9
rand 2..5 # integer between 2..5
rand # float between 0 and 1
Random.rand # basically the same like rand
Select Array Elements
[1,2,3,4,5].sample # picks one element
[1,2,3,4,5].sample 3 # picks a subset of three elements
[1,2,3,4,5].shuffle # reorders the array
The Catch
But all the above methods have one thing in common: They depend on the same global seed. The seed is a non-random number needed to initialize the pseudorandom number generator used by Ruby (a Mersenne Twister). These number generators work pretty well (they are not unbreakable, though). The seeds are typically made out of always changing numbers like the process number and the current time. The global seed is created when the Ruby process starts.
However, it’s also possible to set your own seed using the srand or the Random.srand method. You should not do this. Furthermore, you should not rely on methods that depend on srand at all, because the seed is a global state and gets shared between all libraries loaded.
Because of Ruby’s nature, it’s not uncommon that a single library can change (and potentially break) the behavior of a Ruby application. But you should consider that the use of srand (instead of the newer approach below) could have happened without malicious intentions, which still might open a door for attackers. If your application uses any srand-based method (e.g. Array#sample) and you care about the quality of your randomness, you will have to check all the gems in your Gemfile.lock for the use of srand.
Time Machine
By the way, another feature of srand is that it returns the previously used seed. This might be useful to calculate a new seed based on the old one. On the other hand, it (theoretically) allows an attacker to “steal” your previously used randomness:
$ irb -f
>> def generate_secret_token
>> (0..16).map{[*33..127].sample.chr}.join
>> end # => nil
>> File.write 'secret_token_file', generate_secret_token # writes e.g. "5^hh}kq&294']rQ.|"
>> # other stuff is going on
>> # ...
>> # evil coworker accesses your irb
>> srand srand # capture old seed and use it again
>> generate_secret_token # => "a5^hh}kq&294']rQ."
The Proper Ways to use Randomness in Ruby
SecureRandom
This should be your favorite way of creating a random number:
It also provides the useful .random_bytes and .uuid methods. SecureRandom works different than the usual randomness methods, since it uses external libraries to generate the randomness. At first, it tries to use OpenSSL and if that’s not available, it will try /dev/urandom or advapi32, which are provided by your operating system. It does not have the srand problem and is considered to be strong enough for cryptographic purposes. So why not use it everywhere?
Since the API is not as convenient as the normal randomness methods (e.g. no array core extensions), you can help yourself by building or using simple wrappers!
Random.new
Sometimes, you face a different problem: You might want to generate a random sequences that can be reproduced later. You don’t need to use srand to achieve this, anymore. Newer Ruby versions fix this global state problem by including the Random class that allows you to create instances that represent the state of the prng in an object oriented way. It is used like this:
r = Random.new
r.rand 100
[1,2,3,4,5].sample(random: r)
[1,2,3,4,5].shuffle(random: r)
Pay attention to not do [1,2,3,4,5].shuffle(random: Random.new) – Creating new seeds all the time is less secure than using new numbers out of an existing prng. In these cases, you should use SecureRandom (see above) instead.