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:
nonce
is incremented.The output buffer is encrypted using the ChaCha20 algorithm with
key
andnonce
.
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.
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.