Like other operating systems, Red Hat Enterprise Linux provides a cryptographically-secure pseudo-random number generator (CSPRNG) as part of our kernel. It is intended to be used by cryptographic back-ends and applications requiring cryptographic operations. Unfortunately, there is much mystery around the interfaces provided. While the new random(7) manual page does clarify some aspects, it does not fully address all common questions. In this post, we will make a brief overview of these interfaces, starting from their initialization to their use.

Note that, this post will not get into the internals of a CSPRNG. We will go through these interfaces, intentionally staying on the high-level, without considering internal details, and discuss their usefulness for an application or library that requires access to such a CSPRNG.

Note that these interfaces are a compromise of various schools of thought in the free software ecosystem and carry their historical mistakes for backward compatibility.

How does the kernel initialize its CSPRNG?

The kernel has an “entropy pool,” a place where unpredictable input observed by the kernel is mixed and stored. That pool serves as a seed to the internal CSPRNG, and until some threshold of estimated entropy is reached initially, it is considered uninitialized.

Let’s now see how the kernel initializes its entropy pool.

  1. After the  kernel takes control on power-on, it starts filling its entropy pool by mixing interrupt timing and other unpredictable input.

  2. The kernel gives control to systemd.

  3. Next, systemd starts and initializes itself.

  4. Systemd, optionally, loads kernel modules which will improve the kernel's entropy gathering process on a virtual machine (e.g., virtio-rng).

  5. Systemd loads the rngd.service which will gather additional input entropy obtained via a random generator exposed by hardware (e.g., the x86 RDRAND instruction or similar) and jitter entropy1; this entropy is fed back into the kernel to initialize its entropy pool, typically in a matter of milliseconds.

After the last step, the kernel has its entropy pool initialized, and any systemd services started can take advantage of the kernel’s random generator.

Note that the virtio-rng kernel module loading in step (3), is an optional step which improves entropy gathering in a virtual machine by using the host's random generator to initialize the guest systems in KVM. The rngd.service loading at the final step (4) is what ensures that the kernel entropy pools are initialized on every scenario, and furthermore it continues mixing additional data in the kernel pool during system runtime.

How does the kernel provide access to its CSPRNG?

The Linux kernel, as shipped with Red Hat Enterprise Linux after 7.4, provides the following interfaces for accessing the CSPRNG:

  • getrandom(): A system call which provides random data from the kernel CSPRNG. It will block only when the CSPRNG is not yet initialized.

  • /dev/random: a file which if read from, will output data from the kernel CSPRNG. Reading from this file is blocked until the kernel estimates that enough random events have been accumulated since the last use (many details are omitted for clarity).

  • /dev/urandom: a file which if read from, will provide data from the kernel CSPRNG. Reading from /dev/urandom will never block.

  • AT_RANDOM: a location in process’ memory containing 16-bytes of random data; these are accessible via getauxval() and are always available.

The recommended kernel interface for Red Hat Enterprise Linux 7 is getrandom(); in the following paragraphs we will discuss the weaknesses and strengths of each interface which will make our statement above apparent.

/dev/random

This interface is described in the random(4) manpage as: “/dev/random is suitable for applications that need high-quality randomness, and can afford indeterminate delays.” The interface was designed to work even when the cryptographic primitives it depended on were broken in a catastrophic way.

In practice, because that interface depends on proven primitives like the stream cipher ChaCha20 (in the past it was SHA256), that did not prove to be a reasonable risk, and in practice its dependence on conservative randomness estimation which blocks indefinitely made it unsuitable for applications. Putting its threat model in contrast with existing applications, if the /dev/random threat model applies, the majority of the protocols and algorithms that take advantage of the random generator are already untrustworthy as they depend on the same primitives.

As such, due to its unpredictable semantics, we do not recommend the use of that interface in any scenario.

/dev/urandom

The device /dev/urandom provides access to the same random generator, however it will never block, nor apply any restrictions to the amount of new random events that must be mixed in the kernel entropy pool in order to provide any output. That is quite natural given that the cryptographic primitives used by the Linux kernel random generator, when initialized, can provide enormous amounts (practically unlimited) of output prior to being considered insecure in an informational-theory sense.

Unfortunately /dev/urandom has a quite important flaw. If used early on in the boot process when the random number generator of the kernel is not fully initialized, it will still output data. The "unpredictability" or "randomness" of that data is system-specific.

AT_RANDOM

That interface provides 16-bytes of randomness to each process, following the same rules and having the same limitations as /dev/urandom above, i.e., its value may be derived from an uninitialized entropy pool. Its value remains constant for the lifetime of the process, and is shared with potential children processes after fork(). It can be accessed by using the getauxval() glibc call.

Due to the limitations above, we do not recommend the use of that interface in any scenario.

getrandom()

The getrandom() interface provides non-blocking access to kernel CSPRNG, after the kernel's entropy pool is initialized. When the entropy pool is not initialized the function blocks, making this interface suitable not only for the typical user-space application, but also for applications requiring random data early, during the system's boot process.

Another advantage of this interface is that it does not require a file descriptor to operate. That not only simplifies code using the random generator, but also makes applications more resilient when operating in chroot() environments, because it does not require the presence of a device file.

Which CSPRNG interface should I use in my application?

In order to access a CSPRNG in an application, the use of the kernel’s getrandom() interface is recommended only when no cryptographic library is used. When an application is already using one of our core crypto libraries, we recommend using that library’s provided interfaces. The core crypto libraries provide a CSPRNG which uses getrandom() internally for seeding, and are designed to be efficient, high throughput, and easier to use. The table below summarizes these interfaces; please review the library’s documentation when using these interfaces.

Core crypto component

Interface

Description

OpenSSL

RAND_bytes()

Defined in openssl/rand.h. Note that it has three-state return value and careful error checking is required.

GnuTLS

gnutls_rnd() with GNUTLS_RND_RANDOM as the first argument.

Defined in gnutls/crypto.h.

NSS

PK11_GenerateRandom()

Defined in pk11func.h. Note that it has three-state return value and careful error checking is required.

libgcrypt

gcry_randomize() with GCRY_STRONG_RANDOM as the 3rd argument.

Defined in gcrypt.h.

 

How to use getrandom() in an application safely?

The getrandom() function was introduced to Red Hat Enterprise Linux in 7.4, and is a low-level interface. It is not suitable to be used directly by applications expecting a safe high-level interface, may return less data than requested when reading more than 256-bytes, and this system call is not wrapped by glibc on versions prior to Red Hat Enterprise Linux 8 Beta.

For portable code, that means that it should be accessed via the syscall() interface, and callers should fallback to a different interface (e.g.,  /dev/urandom) if the ENOSYS error code is returned. An illustration of (safe) usage of this system call, is given below.

#include <sys/syscall.h>
#include <errno.h>
#define _sys_getrandom(dst,s,flags) syscall(SYS_getrandom, (void*)dst, (size_t)s, (unsigned int)flags)

static int safe_getrandom(void *buf, size_t buflen, unsigned int flags)
{
 ssize_t left = buflen;
 ssize_t ret;
 uint8_t *p = buf;
 while (left > 0) {
    ret = _sys_getrandom(p, left, flags);
    if (ret == -1) {
     if (errno != EINTR)
        return ret;
    }
    if (ret > 0) {
       left -= ret;
       p += ret;
    }
 }
 return buflen;
}

Don’t leave randomness to chance

Which interface to use, and the limitations of each, can be confusing. I hope this post is useful in helping you to choose the right interface for your use case.


  1. Entropy gathered by measuring timing variance of operations on the local cpu.


关于作者

Nikos Mavrogiannopoulos has a background in mathematics and holds a Ph.D. in cryptography. Spent two decades in software engineering, mostly in security of back-end software; formed the RHEL cryptographic team. I'm now a manager in RHEL Security Engineering.

Read full bio