What is so “secure” about SecureRandom?

As you all know, Ruby comes with a variety of ways to generate random numbers; Random.new, Kernel.rand, the infamous forty_two gem. And then there’s SecureRandom. But why “secure”? Does it mean others are insecure by design?

Kernel.rand relies on a global, non-random seed generated when the Ruby process starts. The seed is fed to the default ruby pseudorandom number generator, which will keep track of it alongside its sequence position to output different random numbers.

$ rand # seed: 2, sequence: 1
> 0.43599490214200376
$ rand # seed: 2, sequence: 2
> 0.025926231827891333

You can also change the seed value with Kernel.srand, which will reset the state of the generator.

$ srand 1
> 2
$ rand # seed: 1, sequence: 1
> 0.417022004702574

Notice the return value of srand? It’s the value of the previous seed. It means that you can (or any other lib your in your project!) know how previous random numbers were generated, and even go as far as to reproduce them all by reseting the generator to its original state.

$ srand 2
> 1
$ rand # seed: 2, sequence: 1
> 0.43599490214200376
$ rand # seed: 2, sequence: 2
> 0.025926231827891333

Random.new runs on the same principles, with the only difference that it lets you set a different seed on each object. Every instance of Random represents the pseudorandom number generator in a given state.

Knowing the state of the generator and being able to reset its state is useful in some circumstances and not bad in itself. Usages such as [1,2,3].sample are perfectly fine. But for anything sensitive such as passwords or session keys, it represents a serious vulnerability. Combine that with the non-random initial seed and you have something that is completely unsuitable for cryptography.

SecureRandom differs from other implementations by relying on OpenSSL and ultimately the operating system to generate randomness. The documentation states that Ruby supports three external secure random number generators;

  • OpenSSL
  • /dev/urandom
  • Win32

Since I’m no Windows user and OpenSSL only adds a layer of indirection on top of /dev/urandom, I’ll be focusing solely on the latter here, but all three offers more or less the same result.

So what the hell is /dev/urandom, and how can it be more secure than our built-in ruby generator? Turns out, the operating system has something that Ruby doesn’t have: an entropy pool.

Entropy is the randomness collected by an operating system or application for use in cryptography or other uses that require random data. This randomness is often collected from hardware sources, either pre-existing ones such as mouse movements or specially provided randomness generators. (Source)

By collecting all sorts of non predictable events (called noise), such as mouse movements, keys typed, and files read, the operating system is able to generate an entropy pool that can later be consumed on demand. Each new event being computed on top of the previous ones, it technically creates an unpredictable entropy unique to each machine.

/dev/urandom is an interface to the kernel pseudorandom number generator, exposed as a readable device. When called, the generator uses entropy available in the pool to generate a stream of random bytes, adding the result back to the pool to make sure we never exhaust it. As an example, here’s how to read 10 random bytes;

$ head -c 10 /dev/urandom | hexdump
> 0000000 f2 a1 90 4d ec c2 a9 ac 72 cf

SecureRandom.hex, base64, and random_number all rely on random_bytes, which reads bytes exactly as we just did, like we’d do for any other file. The current implementation is in C, but doesn’t differ that much from previous Ruby version. Shown below is a simplified version taken from Ruby 1.9.3.

def self.random_bytes(n=nil)
File.open("/dev/urandom", File::RDONLY) do|f|
f.readpartial(n)
end
end

The resulting data is then simply massaged into the desired format depending on what method you called.

And that is why SecureRandom is considered secure. It still rely on a pseudorandom number generator as we would do in ruby land, but it has the benefit of using environmental noise, making it astronomically much more difficult to crack, and technically impossible without gathering the exact same environmental data.

Implementations may differ from one system to another. Linux offers both /dev/random and /dev/urandom (learn the difference here), OSX relies on a “SecurityServer” instead of an entropy pool and some recent Linux distributions offer a getrandom(2) system call to avoid having to read directly from the random device. But as much as they can differ, the core concept stays the same.

Thanks, and go have some cryptographic fun!

Disclaimer: I’m no security expert. I wrote this article wanting to learn more about those concepts, but I do not guarantee any of this information. If you find mistakes or think I misunderstood some elements, please let me know.