Elixir
The Adventures of Generating Random Numbers in Erlang and Elixir
While working in Elixir I needed to generate a random number and to my surprise could not find an easy way to do so without using Erlang (at first - see below). That lead me to learning a few interesting things about random seeds, how to create secure ones and how Erlang handles random.
Erlang comes out of the box with two modules in charge of generating pseudo-random values: random
and rand
.
The Erlang documentation for random
warns that one should use "the improved" rand
instead of random
for generating random numbers. Another (less official) site mentioned that random is scheduled to be removed in Erlang/OTP 20.
I wondered what the difference between rand
and random
was so I fired the Erlang repl (erl
) and typed:
1> random:uniform().
0.4435846174457203
Then I closed and re-opened erl
and ran that same command again:
1> random:uniform().
0.4435846174457203
Surprisingly we get the same answer for random. That's not very random. Turns out random
uses the same seed by default for every VM instance. That's not great, to say the least.
By contrast, the new and improved rand
will use a different seed each time:
Eshell V8.0.2 (abort with ^G)
1> rand:uniform().
0.6015998630734499
After restarting erl
:
Eshell V8.0.2 (abort with ^G)
1> rand:uniform().
0.05250536227383032
Much better.
So that's it right?? Not so fast... The documentation for rand
states that the default seed is not cryptographically strong, and suggests that we use "one of the functions in crypto
" to generate a cryptographically strong seed. There are however no examples whatsoever as to how one would do that and I had to dig deep to discover what follows.
What does it mean for a seed to be safe? (a bit of a tangent)
I found an excellent explanation online by Thomas Pornin that I think explains it better than I could:
When you generate a private key, you do so with a source of randomness. If that source of randomness can output N different streams of bits, then, at most, you may get N different private key. This is where we like to talk of entropy, which is a measure of how big that N is. When the source of randomness is said to offer "100 bits of entropy", then it means that (roughly) N = 2100.
The attacker will want to obtain your private key. If he knows that you use a weak source with a low N (say, only 40 bits of entropy), then he can, on his own machine, enumerate all possible outputs from the random source and work out the corresponding private key.
For instance, suppose that you used a PRNG seeded with the current time, expressed in microseconds. This is the time as known by your machine. The attacker assumes that your machine is reasonably well set with the current time, say within 10 seconds. So, from the point of view of the attacker, the seed for your PRNG is known within a 10 seconds range; since the PRNG use the time in microseconds, that leaves him with N = 10000000 possible seeds. The attacker then says to himself: "IF that guy used as seed value x, THEN his code produced private key value Kx; let's see if that matches his public key... nope. So he did not use x. Let's try again with x+1 (and so on)."
So a weak PRNG is deadly in such situations.
How to generate cryptographically strong seeds with the new rand
library
If you are still stuck in an old version of Erlang you can seed random like this:
erlang
seed = crypto:bytes_to_integer(crypto:strong_rand_bytes(12)).
random:seed(seed).
To generate a cryptographically strong seed for the rand
library we can use the following code:
<<I1:32/unsigned-integer, I2:32/unsigned-integer, I3:32/unsigned-integer>> = crypto:strong_rand_bytes(12).
rand:seed(exsplus, {I1, I2, I3}).
% then we can use rand:uniform as usual and all of our generated numbers will be based of the secure random seed
rand:uniform(1000).
Remember that there is a performance penalty for generating cryptographically secure seeds so you would want to be strategic about seeding your random number generator.
In the meantime a quickrand library has appeared which enforces proper random number seeding: https://github.com/okeuday/quickrand
How to generate a secure random string
To generate a secure random string for an authentication token you can use the following:
base64:encode(crypto:strong_rand_bytes(N)).
Where N is the number of bytes to generate.
Here is an example of generating a 20 byte string by encoding strong_rand_bytes
with base64:
1> base64:encode(crypto:strong_rand_bytes(20)).
<<"XKRL5NDnnuIWo0KD9Taz4A6JSZw=">>
Using rand
in Elixir
Using rand
in Elixir is as easy as calling it as an atom and replacing the :
operator with a .
operator:
:rand.uniform()
In Elixir there is no need to terminate the statement with a .
.
To generate a cryptographically strong random number in Elixir by way of interop to Erlang:
<< i1 :: unsigned-integer-32, i2 :: unsigned-integer-32, i3 :: unsigned-integer-32>> = :crypto.strong_rand_bytes(12)
:rand.seed(:exsplus, {i1, i2, i3})
:rand.uniform(1000) # generate random number from 0 - 1000
This conversion from Erlang to Elixir was not as trivial to me. Particularly the binary pattern matching to 32-bit integers. Fortunately users Ben Wilson (benwilson512
) and zackehh
on the Elixir Slack channel were able to help.
Enum.random
After writing this blog post I discovered a more idiomatic way of generating random numbers in Elixir using the Enum.random
function. The function is described as such
def random(enumerable)
Returns a random element of an enumerable.
Raises Enum.EmptyError if enumerable is empty.
This function uses Erlang's :rand module to calculate the random value. Check
its documentation for setting a different random algorithm or a different seed.
The implementation is based on the reservoir sampling
(https://en.wikipedia.org/wiki/Reservoir_sampling#Relation_to_Fisher-Yates_shuffle)
algorithm. It assumes that the sample being returned can fit into memory; the
input enumerable doesn't have to, as it is traversed just once.
Examples
┃ # Although not necessary, let's seed the random algorithm
┃ iex> :rand.seed(:exsplus, {1, 2, 3})
┃ iex> Enum.random([1, 2, 3])
┃ 2
┃ iex> Enum.random([1, 2, 3])
┃ 1
What is not mentioned in this documentation is that this method can also take ranges. So say we want to generate a random number from 1 to 1,000 we could call it like so:
Enum.random(1..1_000)
Like the documentation suggests, Enum.random
uses Erlang's :rand
module behind the scenes, but does not seed it securely by default. If you are going to use Enum.random
for cryptographic purposes and need it to be secure you should still seed it as in my examples above.
I hope you found this blog post educational and that it shed some light on random seeding concepts in Erlang and Elixir.