Ring is one of the most popular Rust cryptography libraries and it provides a way to securely generate random numbers by using the ring::rand
module. In this post I will show you how to generate random numbers correctly using the types provided by Ring.
PRNGs vs CSPRNGs
Computers are deterministic and therefore can’t produce real random numbers without input from external sources. For this reason random number generators implemented in software libraries are generally pseudo-random number generators (PRNGs). On the other hand cryptography libraries require the use of cryptographically secure pseudo-random number generators (CSPRNGs) because the values need to be completely unpredictable for most cryptographic use cases.
PRNGs and CSPRNGs generally require a seed and then once seeded they produce random values deterministically from that initial seed. Usually CSPRNGs are seeded with an unpredictable random value from the operating system or other external source of entropy and as long as the seed is kept secret then the random numbers generated by the CSPRNG will be unpredictable.
Summary of Types
The ring::rand
module contains the following types and functions:
struct SystemRandom – The CSPRNG used for generating random numbers where the source of entropy is the operating system. Each application should only create a single instance of SystemRandom
and use it throughout the whole application. SystemRandom
is thread safe and so it can be shared across multiple threads safely.
trait SecureRandom – Defines a fill
method for filling bytes into a buffer and is implemented by the SystemRandom
struct.
struct Random – Wraps a random number and supports only exposing it once.
trait RandomlyConstructable – The type wrapped by Random
must implement RandomlyConstructable
. The Ring library has implemented RandomlyConstructable
on byte arrays [u8, n]
. This has the effect of restricting the type of random values to byte arrays.
fn generate – The function used to generate random numbers which takes a reference to a SecureRandom
and returns a Result
containing a Random<T>
. See the full function definition here:
pub fn generate<T: RandomlyConstructable>(
rng: &dyn SecureRandom
) -> Result<Random<T>, Unspecified>
Rust Imports
Let’s start by importing the required types into our project.
use ring::rand;
use ring::rand::SystemRandom;
use ring::rand::Random;
use ring::rand::SecureRandom;
Create an Instance of SystemRandom
To create an instance of the SystemRandom
CSPRNG we simple call the SystemRandom::new()
function. The new function is guaranteed to always succeed and have low latency. This is because most of the initialisation logic is deferred to the SecureRandom::fill
method.
let sys_random = SystemRandom::new();
Generate Random Numbers
We can generate random numbers in two ways using the ring::rand
module; by using the SecureRandom::fill
method or by using the rand::generate
function.
To use the fill method we first need to create a buffer which is an array of bytes which will be used to store the random data. We then call fill, passing in a reference to the buffer. The buffer needs to be mutable to support updating the buffer in place. After the call to fill
, the SystemRandom
instance will have updated the buffer with random bytes sourced from the OS. The fill method will internally call the OS to get the random data. For example on Linux it will use the getrandom
syscall and on MacOS will use the SecRandomCopyBytes
syscall.
In the below example I debug print the random bytes and then convert the bytes to a u32 before printing a second time.
// initialise a buffer with zeros
let mut buffer = [0u8; 4];
// fill the buffer with random bytes
sys_random.fill(&mut buffer).unwrap();
println!("{:?}", buffer);
println!("{:?}", u32::from_be_bytes(buffer));
In this next example we use the higher level generate
function which handles creating the buffer for us. We pass in a reference to the SystemRandom
and it simply returns the random value wrapped in a Random
instance. Calling expose
on the Random will consume it and so expose can only be called once. This design can help prevent accidental reuse of the same random value which can have big consequences in cryptographic systems. In these examples the byte array used to store the random value is set to size 4 but we could pick any size to suite our needs.
// generate the random number
let result : Random<[u8; 4]> = rand::generate(&sys_random).unwrap();
// get the value
let rand_bytes = result.expose(); // can only be called once
println!("{:?}", rand_bytes);
println!("{:?}", u32::from_be_bytes(rand_bytes));
Full Sample Code
Here is the full code sample for reference.
use ring::rand;
use ring::rand::SystemRandom;
use ring::rand::Random;
use ring::rand::SecureRandom;
fn main() {
// Create a secure random number generator
let sys_random = SystemRandom::new();
// initialise a buffer with zeros
let mut buffer = [0u8; 4];
// fill the buffer with random bytes
sys_random.fill(&mut buffer).unwrap();
println!("{:?}", buffer);
println!("{:?}", u32::from_be_bytes(buffer));
// generate the random number
let result : Random<[u8; 4]> = rand::generate(&sys_random).unwrap();
// get the value
let rand_bytes = result.expose(); // can only be called once
println!("{:?}", rand_bytes);
println!("{:?}", u32::from_be_bytes(rand_bytes));
// generate more random numbers using the same SystemRandom object
let result : Random<[u8; 4]> = rand::generate(&sys_random).unwrap();
let rand_bytes = result.expose();
println!("{:?}", rand_bytes);
println!("{:?}", u32::from_be_bytes(rand_bytes));
}
Conclusion
In this post we covered how to securely generate random numbers using the Ring cryptography library. We showed how to create an instance of the SystemRandom
type and how to use either SecureRandom::fill
or rand::generate
to create random bytes and then convert into a number if required. If you have any questions on this topic, feel free to reach out.