Randomness APIs serve two different contracts. Deterministic pseudo-random number generators are useful for repeatable tests and simulations. Security-sensitive keys, nonces, tokens, salts, and seeds require bytes from the kernel’s cryptographically secure random number generator (CSPRNG).
C Library Generators #
rand() advances hidden process state and returns an integer from 0 through
RAND_MAX. srand(seed) selects a repeatable sequence for a particular C library
implementation; omitting it behaves as though the seed were 1. ISO C does not
specify the algorithm or require the same sequence across implementations, so a
test that needs cross-platform reproducibility should choose and record an explicit
algorithm.
random() and srandom() provide another deterministic family. glibc implements
random() with additive-feedback state and allows callers to switch state arrays
with initstate() and setstate(); random_r() makes that state explicit.
rand_r() stores only an unsigned int of state and is obsolete in POSIX, so it is
not a suitable design for independent high-quality streams.
The drand48() family specifies a 48-bit linear congruential recurrence:
X(n + 1) = (0x5DEECE66D * X(n) + 0xB) mod 2^48drand48() returns a double in [0.0, 1.0), lrand48() returns a long in
[0, 2^31), and mrand48() returns a signed result in [-2^31, 2^31).
erand48(), nrand48(), and jrand48() use a caller-supplied three-element state;
the _r interfaces use an explicit reentrant state structure.
Every family above is predictable after an attacker learns enough state or output. Seeding one with the current time, a PID, or one kernel-random value does not turn its subsequent output into a cryptographic generator.
Kernel Entropy and the CSPRNG #
Linux mixes timing and device data into an input pool and conservatively credits unpredictable contributions during initialization. Once enough entropy has been credited, the kernel finalizes pool state into a CSPRNG key. Current upstream code uses a ChaCha-based fast-key-erasure generator and periodically refreshes its key from the pool. Applications consume CSPRNG output; they do not drain raw hardware noise one byte for each output byte.
getrandom(2) is the primary Linux interface because it needs no pathname or file
descriptor:
getrandom(buf, len, 0)waits until the CSPRNG is initialized.GRND_NONBLOCKreturnsEAGAINinstead of waiting during that early-boot state.- An initialized urandom-source request of at most 256 bytes returns the requested
length and is not interrupted by a signal. Larger requests can return short or
fail with
EINTR. getentropy(3)is a libc interface limited to 256 bytes. It returns zero only after filling the entire buffer and otherwise returns-1.
GRND_RANDOM is a legacy compatibility flag, not a higher-security mode. Before
Linux 5.6, random-source reads could block according to the entropy estimate.
Current upstream random.c treats /dev/random like getrandom(..., 0) and blocks
only until initialization; old kernels retain the old behavior. Portable new code
should use flags 0.
Device Semantics and Early Boot #
/dev/urandom never waits for initialization. Current kernel source equates it with
GRND_INSECURE, so an unusually early read can occur before the CSPRNG has a secure
seed. getrandom(..., 0) avoids that race by waiting. After initialization,
/dev/urandom, /dev/random, and ordinary getrandom output come from the kernel
CSPRNG; waiting for an entropy counter to refill does not improve a generated key.
Since Linux 5.6, /dev/random no longer blocks after initialization and its
O_NONBLOCK setting matters only while initialization is incomplete. The historical
distinction still matters when software must run on older kernels. Writes to either
device mix data into the pool but do not credit entropy unless a privileged
random-device ioctl explicitly does so.
Secure Key Generation #
Always check errors and lengths. A loop remains correct if the buffer later grows beyond the small-request guarantee:
#include <errno.h>
#include <stddef.h>
#include <sys/random.h>
static int
random_bytes(void *buffer, size_t length)
{
unsigned char *p = buffer;
while (length != 0) {
ssize_t n = getrandom(p, length, 0);
if (n > 0) {
p += (size_t)n;
length -= (size_t)n;
continue;
}
if (n < 0 && errno == EINTR)
continue;
return -1;
}
return 0;
}
unsigned char key[32];
if (random_bytes(key, sizeof key) < 0) {
/* Abort key creation; never continue with an unfilled key. */
}Generate key bytes directly rather than feeding them through rand(). For an
unbiased integer below a bound, use rejection sampling; value % bound is biased
unless the source range is an exact multiple of the bound. Secret lifetime, secure
erasure, nonce uniqueness, and file permissions remain separate responsibilities
after generation.
The upstream getrandom(2)
and random(4) pages define
the userspace ABI. Current implementation details and post-5.6 device equivalence
are documented in the kernel
drivers/char/random.c
source.
Comments
Read or comment on GitHub (sign-in required to comment)