OS random number generators¶
getrandom(), arc4random and BCryptGenRandom: the generator built into
the operating system, and the one your code should ask for keys, nonces and
tokens.
Status and standards: see Random number generation in the main table.
What it is¶
Every major OS runs a cryptographic random generator in the kernel. It collects unpredictable events, hashes them into a seed, and uses the seed as the key of a fast cipher-based generator. Programs ask it for bytes with a system call:
| System | Ask for bytes with | Generator behind it |
|---|---|---|
| Linux | getrandom(), /dev/urandom, /dev/random |
ChaCha20 since 4.8 (2016) |
| OpenBSD | arc4random(), getentropy() |
ChaCha20 since 5.5 (2014), RC4 before |
| FreeBSD | arc4random(), getrandom() |
ChaCha20 in libc since 12.0 (2018) |
| macOS | arc4random(), getentropy() |
AES since 10.12 (2016); Fortuna-derived kernel generator |
| Windows | BCryptGenRandom |
SP 800-90 CTR_DRBG |
In Python, os.urandom and the secrets module (3.6+) sit on top of these.
Since Python 3.6, os.urandom on Linux calls getrandom() and waits until
the kernel generator is seeded; on Windows it calls BCryptGenRandom since
3.11.
How it works¶
Linux¶
Theodore Ts'o's driver has been in Linux since 1.3.30 (1995). It had two
outputs. /dev/random kept an estimate of how much entropy the pool held,
subtracted from it on every read, and blocked when it ran low.
/dev/urandom never blocked. Since then:
- 3.17 (2014): the
getrandom()system call, asked for by the LibreSSL developers. It needs no file descriptor, and it waits only once: until the pool has been seeded at boot. - 4.8 (2016): Ts'o replaced the non-blocking pool with a ChaCha20 generator and stopped pretending to track how much entropy it used up.
- 5.6 (2020): Andy Lutomirski removed the blocking pool.
/dev/randomnow waits until the generator is seeded once, then reads exactly like/dev/urandom.getrandom(GRND_INSECURE)was added for the few callers that want bytes before that. - 5.17 and 5.18 (2022): Jason Donenfeld replaced SHA-1 with BLAKE2s for
extracting from the input pool. He also made
/dev/urandomwait for seeding, but that hung qemu boots on arm, m68k and other machines without a cycle counter, and Linus reverted it. What stayed: a/dev/urandomread before seeding first runs the CPU-jitter entropy routine, which on x86, and likely any machine with a working cycle counter, seeds the generator in about a second.
Today a BLAKE2s hash collects the input, and once 256 bits of entropy have been credited it becomes the ChaCha20 key, refreshed every 60 seconds. All three interfaces read from that one generator.
The others¶
- OpenBSD:
arc4randomruns ChaCha20 inside each process and reseeds from the kernel withgetentropy()on a regular basis and afterfork(). - macOS: Apple describes the kernel generator as Fortuna-derived,
aiming at 256-bit security. It is seeded from the Secure Enclave's
hardware generator, boot-time jitter, interrupt timing, a seed file and,
on Intel Macs, RDSEED and RDRAND.
arc4randomused RC4 until macOS 10.12 and AES after. - Windows: call
BCryptGenRandom(NULL, buf, len, BCRYPT_USE_SYSTEM_PREFERRED_RNG). The default provider has implemented CTR_DRBG from SP 800-90 since Vista SP1. The olderCryptGenRandomis deprecated.
Why "urandom is fine"¶
The old /dev/random treated entropy like fuel that reading burns. It
isn't. Once a generator holds a 256-bit key that nobody else knows, its
output doesn't get weaker as you read more of it. That is the same thing
every stream cipher relies on. The Linux man page calls /dev/random a
legacy interface and says /dev/urandom is enough for everything except
early boot.
The early-boot caveat is real. Right after power-on, especially on a small
device with no disk, no keyboard and no cycle counter, the pool may not be
seeded yet, and older /dev/urandom answered anyway. Routers and firewalls
that made their SSH and TLS keys on first boot did exactly that. In 2012
researchers scanned the whole internet and factored the RSA keys of 0.50%
of the TLS hosts they found (Mining your Ps and Qs).
getrandom() waits instead, and so does os.urandom in Python 3.6+. On
current kernels /dev/urandom still doesn't wait on machines where the
jitter routine can't run; the kernel logs "uninitialized urandom read" when
that happens. The kernel's own comments say to use getrandom() for this
reason.
When to use it¶
- For every secret: keys, nonces, IVs, salts, session IDs, password-reset tokens, generated passwords.
- Through your language or library:
secretsandos.urandomin Python,crypto/randin Go,randombytes_bufin libsodium. OpenSSL 3 seeds its own DRBG from the OS. - Not Python's
random, C'srand()or anything seeded with the time. See Retired generators. - If a FIPS 140-3 module is required, it runs an approved NIST DRBG seeded from a validated entropy source.
Example (Python)¶
import secrets, string
key = secrets.token_bytes(32) # 256-bit key, straight from os.urandom
link = secrets.token_urlsafe(32) # password-reset token, 32 random bytes
pin = f"{secrets.randbelow(10**6):06d}" # 6-digit code, no modulo bias
abc = string.ascii_letters + string.digits
pw = "".join(secrets.choice(abc) for _ in range(16))
print(len(key), len(link), len(pin), len(pw)) # 32 43 6 16
The values change every run; only the lengths are fixed. token_urlsafe
base64-encodes its 32 bytes into 43 characters.
Security notes¶
- Call
getrandom()or your library rather than opening/dev/urandomyourself. A file read can fail when file descriptors run out or/devis missing in a chroot, and it doesn't wait at early boot. - For a number below n, use
secrets.randbelow(n)orarc4random_uniform(n), notrandom_value % n, which favours small numbers when n doesn't divide the range evenly. - A virtual machine restored twice from one snapshot runs twice with the same generator state. Hyper-V and QEMU expose a VM generation ID, and since Linux 5.18 the kernel reseeds when it changes.
- A generator that keeps state in user space must reseed after
fork(), or parent and child print the same bytes. OpenBSD'sarc4randomdoes.
See also¶
- ChaCha and Salsa20: the Linux generator's cipher, with fast key erasure
- NIST DRBG: HMAC_DRBG, CTR_DRBG and the FIPS rules
- Retired generators: what not to use, and what went wrong
- BLAKE: BLAKE2s, the Linux input-pool hash
References¶
- Linux man pages: random(4) / getrandom(2) /
drivers/char/random.c - Linux commits: getrandom (3.17) / ChaCha20 CRNG (4.8) /
/dev/randomalmost like/dev/urandom(5.6) / GRND_INSECURE (5.6) / BLAKE2s (5.17) / block in/dev/urandomand its revert / opportunistic init (5.18) / vmgenid (5.18) - OpenBSD
arc4random(3)/ FreeBSDarc4random(3)/ FreeBSDgetrandom(2)/ Apple Libcarc4random.3 - Apple Platform Security: Random number generation
- Microsoft:
BCryptGenRandom - Python:
os.urandom/secrets/ PEP 506 / PEP 524 - Heninger, Durumeric, Wustrow and Halderman, "Mining Your Ps and Qs" (USENIX Security 2012)