Making /dev/random behave like getrandom(2) will finally put to rest one of the most frustrating arguments in the public understanding of cryptography. Please do it.
The question of whether "true random numbers" as defined by this weird government standard exist.
More fundamentally, an important conceptual part of crypto is that you can use a random key of a very small, fixed size, like 16 bytes, to generate an infinite amount of output, such that knowing any part of the output doesn't help you guess at any other part of the output nor the input key. If true, this is an amazingly powerful property because you can securely exchange (or derive) a short key once and then you can send as much encrypted data back and forth as you want. If you needed to re-seed the input with as many bits as you take from the output, disk encryption wouldn't be sound (you'd need a key or passphrase as long as your disk), SSL would be way more expensive (and couldn't be forward-secure) if it even worked at all, Kerberos wouldn't work, Signal wouldn't work, etc.
The claim of /dev/random and this wacky government standard is that in fact disk encryption, SSL, etc. are flawed designs, good enough for securing a single communication but suboptimal because they encrypt more bits of data than the size of the random key, and so when generating random keys, you really ought to use "true random numbers" so that breaking one SSL connection doesn't help you break another. Whether it's a pen-and-paper cipher like Vigenère or a fancy modern algorithm like AES, anything with a fixed-size key can be cryptanalyzed and you shouldn't provide too much output with it, for your secure stuff you must use a one-time pad. The claim of the cryptography community is that, no, in fact, there's nothing flawed about this approach and stretching fixed-size keys is the very miracle of cryptography. We know how to do it securely for any worthwhile definition of "securely" (including definitions where quantum computers are relevant) and we should, because key-based encryption has changed our world for the better.
> ... like 16 bytes, to generate an infinite amount of output, such that knowing any part of the output doesn't help you guess at any other part of the output nor the input key.
Isn't that the theory behind every stream cipher? (And stream ciphers are generally just 'simplified' one-time pads.)
That's what OpenBSD's arc4random(4) start as: the output of RC4.
Yep. The OpenBSD bootloader reads an entropy file from hard disk, mixes it with RDRAND from CPU (if available) and passes it to the Kernel.
The Kernel starts an ChaCha20 stream cipher with this supplied entropy while constantly mixing in timing entropy from devices.
This chipherstream supplies the Kernel with random data and once userland is up this is good enought and also used for /dev/random and /dev/urandom, which on OpenBSB is the same device(non blocking).
Now the fun part: When a userland process gets created it has a randomdata ELF segment that the Kernel fills and which is used as entropy for a new ChaCha20 stream, just for the application should it decide to call arc4random or use random data in any other way (like calling malloc or free, which on OpenBSD make heavy use of random data).
The .openbsd.randomdata ELF section is used for RETGUARD. arc4random(3) uses the getentropy(2) system call for seeding, and minherit(2)+MAP_INHERIT_ZERO for consistent, automatic reinitialization on fork.
Interestingly, Linux provides 128 bits of random data on exec through the ELF auxiliary vector mechanism. (https://lwn.net/Articles/519085/) Between the disappearance of the sysctl(2) syscall and the addition of getrandom(2), it was the only way to acquire strong seed entropy without opening a file resource.
EDIT: Which makes me curious how Linux filled AT_RANDOM for init(1) and other early boot time processes. But not curious enough to comb through old code...
> EDIT: Which makes me curious how Linux filled AT_RANDOM for init(1) and other early boot time processes. But not curious enough to comb through old code...
It uses get_random_bytes(), which is documented as "equivalent to a read from /dev/urandom."
In 2012, first release 5.3 in 2013. Added by Matthew Dempsky. It was used for the per-shared object stack protector cookie extended for the per-function cookies required for RETGUARD.
Linux has an equivalent feature available through the "auxiliary vector," a set of data passed as the secret fourth parameter to main() / on the stack at program startup (after the secret third parameter, envp). http://man7.org/linux/man-pages/man3/getauxval.3.html and https://lwn.net/Articles/519085/ have a description of the auxiliary vector. One key, AT_RANDOM, contains 16 bytes (128 bits) of random data which libc uses for stack protector cookies. glibc uses this to implement stack and pointer protector cookies.
(Unfortunately, glibc uses this data directly as stack and pointer protector cookies, instead of deriving something from it, which means it feels a little risky to use this to initialize a userspace PRNG. I guess you shouldn't be leaking cookies....)
Linux added this in v2.6.29 (2009) in https://github.com/torvalds/linux/commit/f06295b4 , and glibc in 2009 added support for using it if available to set up cookies (it previously read from /dev/urandom). That said, I don't really think the "we're the only OS" game is a game worth playing - if it's a security improvement, it's best if everyone has it, regardless of OS!
> The original version of this random number generator used the RC4 (also known as ARC4) algorithm. In OpenBSD 5.5 it was replaced with the ChaCha20 cipher, and it may be replaced again in the future as cryptographic techniques advance. A good mnemonic is “A Replacement Call for Random”.
The idea of "randomness" being "used up", and then "running out of randomness", somehow.
So let's look at how a hypothetical CSPRNG might work. We get our random numbers by repeatedly hashing a pool of bytes, and then feeding the result, and various somewhat random events, back into the pool. Since our hash does not leak any information about the input (if it did, we'd have much bigger problems), this means attackers must guess, bit for bit, what the value of the internal pool of entropy is.
This is essentially how randomness works on Linux (they just use a stream cipher instead for performance)
This clarifies a few things:
1. even if you assume intels randomness instructions are compromised, it still is not an issue to stirr them into the pool. Attackers need to guess every single source of randomness.
2. "Running out of randomness" is nonsensical. If you couldn't guess the exact pool before, you can't suddenly start guessing the pool after pulling out 200 exabytes of randomness either.
There is actually a sense in which you can "use up" or "run out" of randomness; it's just almost the exact opposite of how unix-style /dev/random design thinks about it.
Basically, you[0] should think of /dev/random as having a front buffer and a back buffer. The back buffer has a certain amount of entropy in it, but you can't take part of that entropy out; the only thing you can do with it is add entropy or empty the entire back buffer into the front buffer. The front buffer doesn't have a entropy amount per se, what it has is a security rating[1]; when you empty the back buffer into it, its security rating increases up to (not plus) the number of bits in the back buffer (this is not additive; a 256-bit front buffer combined with a 256-bit back buffer produces a front buffer with 256 bits, not 512) and the back buffer goes to zero. If you keep dumping the back buffer into front buffer whenever it reaches 64 bits, you'll never have a RNG that's more than 64-bit secure.
Reading from /dev/random doesn't deplete the front buffer (because CSPRNG) or the back buffer (because it doesn't interact with the back buffer). A memory-read attack on the other hand basically sets both buffers to zero - you have to start all over again.
So you can "use up" randomness by constantly wasting it to refresh a insufficiently-strong front buffer. And you can "run out" if someone is able to read your buffers (or brute force a weak buffer in, say, 2^64 CSPRNG invocations).
0: As a designer. As a user, you should treat /dev/random (like any cryptographic primitive) as something that will look perfectly secure from the outside even if it's hopelessly broken, and investigate the details of the specific implementation you're using accordingly.
1: Just like a cryptographic algorithm; the lowest rating involved determines how secure your system is. A 512-bit RNG with a 64-bit cypher is only 64-bit secure, and a 512-bit cypher fed by a 64-bit RNG is also only 64-bit secure.
> 2. "Running out of randomness" is nonsensical. If you couldn't guess the exact pool before, you can't suddenly start guessing the pool after pulling out 200 exabytes of randomness either.
Not entirely.
/dev/random and arc4random(4) under OpenBSD originally used the output of RC4, which has a finite state size:
Rekeying / mixing up the state semi-regularly would reset things. It's the occasional shuffling that really helps with forward security, especially if a system has been compromised at the kernel level.
No, Arc4random didn't reveal its internal RC4 state as it ran, in the same sense that actually encrypting with RC4 doesn't deplete RC4's internal state.
> No, Arc4random didn't reveal its internal RC4 state as it ran ...
Yes, I know. Where did I say anything about revealing? My comment was about 'running out', which is (IIRC) a limitation of some random number generators because of how they handle internal state. Now, that state may have many, many bits, but it is still finite. An analogy I've seen is like having a (paper) codebook.
Of course, if a system is compromised, and the attacker can read kernel memory, they can probably then recreate the stream--which is why (e.g.) OpenBSD stirred things up every so often.
That's why OpenBSD cut away the start of the RC4 stream (don't remember how many bytes) to make backtracking harder.
But the point is mood b.c. the stream cipher used switched from RC4 to ChaCha20 like 5 years ago. And there is no side channel attack on ChaCha20, yet.
why OpenBSD cut away the start of the RC4 stream (don't remember how many bytes) to make backtracking harder
Yes, everybody does that. But how many bytes you drop matters; over the years the recommendations have gone from 256 bytes to 512 bytes to 768 bytes to 1536 bytes to 3072 bytes as attacks have gotten better.
That's obviously true, but in the most unhelpful way possible, where you introduce a complex additional topic without explaining how it doesn't validate the previous commenter's misapprehension about how "state" works in this context.
I wasn't entirely sure if the previous commenter was confused or merely saying things in a confusing way. The fact is that with a small entropy pool and a leaky mechanism like RC4, you absolutely can run out of entropy.
that /dev/random must be better than /dev/urandom because it blocks until there's "enough" entropy (where "enough" is a huge misnomer and not really something you need as long as you're using a modern OS)