How to Read getrandom and dev urandom in C
Obtaining cryptographically secure random bytes in the C programming
language requires querying operating system entropy sources rather than
relying on standard pseudo-random functions like rand(). On
modern Unix-like and Linux systems, developers typically achieve this
either by invoking the dedicated getrandom() system call or
by directly opening and reading the /dev/urandom special
device file. This guide explains how to implement both approaches
directly in C, detailing their syntax, error handling, and practical
differences.
Method 1: Using the
getrandom() System Call
The getrandom() system call (available on Linux kernel
3.17 and newer via glibc 2.25+) is the preferred method for
obtaining entropy. Unlike opening device files, it does not consume a
file descriptor, cannot be blocked by chroot environments,
and naturally prevents reads if the kernel's entropy pool has not yet
been initialized.
Implementation
To use getrandom(), include
<sys/random.h>. The function returns the number of
bytes read or -1 if an error occurs. Like standard I/O
operations, it can be interrupted by signals (EINTR), so
reads should be wrapped in a loop until the target buffer is filled.
#define _GNU_SOURCE
#include <sys/random.h>
#include <stddef.h>
#include <errno.h>
int secure_random_getrandom(void *buffer, size_t length) {
size_t total_read = 0;
char *buf = (char *)buffer;
while (total_read < length) {
ssize_t result = getrandom(buf + total_read, length - total_read, 0);
if (result < 0) {
if (errno == EINTR) {
continue; /* Interrupted by system signal, retry */
}
return -1; /* Failed to read entropy */
}
total_read += result;
}
return 0; /* Success */
}The third argument specifies flags. Passing 0 defaults
to standard blocking behavior, which waits only if the CSPRNG has not
yet initialized during early boot. Passing GRND_NONBLOCK
will return an error (EAGAIN) instead of waiting.
Method 2: Reading
Directly from /dev/urandom
Reading /dev/urandom is the traditional, widely portable
POSIX approach. It treats the kernel's CSPRNG as a file stream.
Implementation
The process involves opening the device file with
open(), reading the requested byte length with
read(), and closing the descriptor with
close(). Using low-level POSIX file descriptors is
preferred over FILE* streams
(fopen/fread) to avoid unnecessary userspace
buffering.
#include <fcntl.h>
#include <unistd.h>
#include <stddef.h>
#include <errno.h>
int secure_random_urandom(void *buffer, size_t length) {
int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return -1;
}
size_t total_read = 0;
char *buf = (char *)buffer;
while (total_read < length) {
ssize_t result = read(fd, buf + total_read, length - total_read);
if (result < 0) {
if (errno == EINTR) {
continue;
}
close(fd);
return -1;
}
total_read += result;
}
close(fd);
return 0;
}The O_CLOEXEC flag prevents the file descriptor from
leaking into child processes created via exec().
Key Differences and Best Practices
- File Descriptors:
getrandom()does not allocate a file descriptor, eliminating failures caused by process file descriptor limits (EMFILEorENFILE). - Security During Boot:
/dev/urandomcan potentially return bytes before the kernel pool is fully seeded on early system boot.getrandom()without flags automatically blocks until the kernel CSPRNG is safe. - Sandboxing: In heavily sandboxed systems,
containers, or restricted
chrootenvironments,/dev/urandommight not exist inside the filesystem tree.getrandom()functions independently of the filesystem layout. - Portability: For Linux-specific modern
applications, use
getrandom(). For systems like older BSDs, macOS, or legacy Linux targets, fallback logic utilizing/dev/urandomis standard practice.