Linux 随机性 API

最后更新: 2024-10-22 20:12:02 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

随机性 API 服务于两种不同契约。 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 库生成器 #

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:

text
X(n + 1) = (0x5DEECE66D * X(n) + 0xB) mod 2^48

drand48() 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.

内核熵与 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_NONBLOCK returns EAGAIN instead 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.

设备语义与早期启动 #

/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.

安全密钥生成 #

Always check errors and lengths. A loop remains correct if the buffer later grows beyond the small-request guarantee:

c
#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. */
}

应直接生成密钥字节,而不是将它们传给 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.

评论

在 GitHub 上阅读或评论(发表评论需要登录)