Cryptographically Secure Pseudo Random Number Generator

This document describes the design of Zircon's Cryptographically secure pseudo random number generator (CPRNG), including its algorithm, (re)seeding process, and entropy sources.

Introduction

Zircon's built-in CPRNG provides cryptographically secure pseudorandom data in a non-blocking fashion. User space programs can access them through the zx_cprng_draw() syscall.

Zircon's CPRNG only trusts entropy sources directly accessible from within the kernel because anything outside the kernel such as the drivers, which are considered userspace programs, cannot be trusted. For the CPRNG to function properly and securely, at least one of these sources is required. However, userspace programs may inject additional entropy to CPRNG through the zx_cprng_add_entropy() syscall.

Algorithms

Zircon's CPRNG is a pseudorandom number generator. Its implementation is located at zircon/kernel/lib/crypto. It supports two operations, Draw() and AddEntropy(), which corresponds to the two syscalls mentioned above. The internal state consists of a 256-bit key and a 128-bit nonce. The key must be kept secret because the CPRNG output can be reliably predicted with the knowledge of it. At the beginning, key is initialized with some random bytes (see next section) and nonce is initialized as 0.

When the Draw() method is called:

  1. nonce is incremented.

  2. The output buffer is encrypted using the ChaCha20 algorithm with key and nonce.

Here nonce is incremented for every Draw() request to ensure different results. The caller provides a buffer to perform the encryption in-place. Any existing data in the buffer is used since they does not affect the security properties.

When there is a AddEntropy() request, the key is updated by mixing additional entropy with the old key:

k<sub>new</sub> = H(e || k<sub>old</sub>)

where k<sub>old</sub> and k<sub>new</sub> are the old and new key, respectively, e is the input bytes, H is the SHA256 hash function and || denotes concatenation. The old key is included in the hash to ensure that callers, e.g. userspace programs that call zx_cprng_add_entropy(), cannot purge the old key and replace it with something these programs control.

Seeding and Reseeding

A call to the AddEntropy() method performs the initial seeding of the Zircon CPRNG. The initial seeding is needed for virtual memory ASLR, so the first call to the AddEntropy() method occurs very early in the boot sequence before the userspace starts. The initial seeding is required for the CPRNG to function as the Draw() method blocks until enough entropy is added.

After the initial seeding, a thread is created to reseed the CPRNG every 30 seconds by calling the AddEntropy() method. This ensures forward secrecy (a guarantee of secrecy for all the CPRNG's previous output since the last reseed, even if its internal state is compromised).

Entropy sources

There are several entropy sources Zircon's CPRNG can utilize for seeding and reseeding:

  • Entropy from kernel cmdline option kernel.entropy-mixin, documented in kernel_cmdline.md.

  • Entropy from hardware RNG such as the RDSEED instruction on x86 devices and other hardware specific RNGs.

  • Jitter Entropy

The kernel cmdline is only used at initial seeding because it is a constant passed in at boot for one-time use only. The entropy from hardware and jitter entropy can be used for both initial seeding and reseeding. To ensure the CPRNG is sufficiently (re)seeded from the selected entropy sources, you can use the kernel cmdline kernel.cprng-(re)seed-require.* options. For more information, see kernel_cmdline.md.

There may be other available entropy sources such as a trusted platform module (TPM), but we do not currently have a strong framework in place for userspace programs to securely communicate with the CPRNG subsystem in the kernel.