Ruby and Random
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.
$ gem install forty_two
$ irb
>> require '42' # true
>> rand 100 # => 42
>> [1,21,42,21,1].sample # => 42
TL;DR
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:
Creating 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
Selecting 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 have been around for a while and work well (they are not unbreakable, though). The seeds are typically made out of values like the process number or the current time in nano seconds. 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:
require 'securerandom'
SecureRandom.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 84
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.
Brian M | February 03, 2013
That is useful to know, but some of your examples were related to malicious intrusion into your code. (By gems or by evil coworkers.) You shouldn't expect SecureRandom to secure you against anyone who can put code into your app. One of the many things they could do: monkeypatch SecureRandom.random_number to return whatever they want.
J-_-L | February 09, 2013
Hi Brian,
thank you for your comment. You are totally right that it's not possible to trust unknown Ruby code, fer example, the one I used in the "srand srand" example. However, the usage of a plain "srand" is not considered as code smell, yet, but I am trying to highlight that it actually is one!