Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[DEV] random: use a ChaCha20-based internal CSPRNG
futils_random_uint{8,16,32,64} return values from a CSPRNG, aka. cryptographically secure random number generator, that is, a well seeded, secure RNG, that don't leak its internal state in its outputs, and prevent recovering previous generated values, even if its internal state is leaked somehow. As it requires doing syscalls to use operating system's CSPRNG, there's a cost of retrieving a random value. A faster, still secure, PRNG would be useful. Here a likely cryptographically secure PRNG is built from ChaCha20, following the construct used on OpenBSD's arc4random(). It uses ChaCha20, as defined by RFC 8439, which is a stream cipher that uses a 256bits key (+ a 96bits nonce): https://tools.ietf.org/html/rfc8439 https://cr.yp.to/chacha.html Here, we're only interested in its key stream, and skipping the encryption part (thus it can be viewed as encrypting a strings of 0s). ChaCha20 is widely used in various CSPRNG. In particular it's used in OpenBSD's arc4random implementation, which this implementation is modeled on. The CSPRNG built here from ChaCha20 should have a 256bits security strength, and a 256 + 96 bits state (constant and counter in ChaCha20 state don't contribute much). The CSPRNG is seeded on first use with the OS CSPRNG discussed above, ensuring the state contains 256 + 96 bits of entropy. No effort is done to prevent data stream to repeat, because one would expect the PRNG to have a period equal to 2^256 - 1, meaning an insane number of call to futils_random{8,16,32,64}() would be needed for the random number sequence actually repeat. Below is a crude comparison between previous implementation and the new one, made on a x86_64 computer running Ubuntu 18.04, made with dedicated test programs. Results are in MiB/s futils_random8() futils_random_bytes() urandom 0.347 220.020 urandom (pool,noplt) 50.923 231.965 getrandom (p,noplt) 60.149 225.354 chacha20 70.939 398.218 Pool buffer size is kept 512 bytes as it's seems to be the sweet spot again: PRNG speed +-------------------------------------------------------------------------------------+ | ++ + + + + + | | getrandom (per thread,tls) ***A*** | | ChaCha20 (per thread,tls) ###B### | | | | ###################B | 70 |-+ #########B################## +-| | B#########B######### | | ## | | # | | B | | ## | | B | | # ****A*************************************A | 60 |-+ # ********** +-| | # **A**** | | # ***** | | # A** | | # ** | | # * | | # A | | # * | | # * | 50 |-+ # * +-| | # * | | # * | | #* | | BA | | * | | * | | * | 40 |-+ * +-| | * | | * | | * | | A | | | | ++ + + + + + | +-------------------------------------------------------------------------------------+ 64 256 512 1024 2048 4096 128 pool size (bytes) BEWARE BEWARE BEWARE BEWARE BEWARE BEWARE BEWARE BEWARE The state *is not reseeded on fork()*, so child and parent will produces the same random number sequences. Also a proper CSPRNG should be reseeded regularly to ensure the few bits leaked in its output cannot be used to recover its state. Or in the event its state is fully leaked somehow, prevent an attacker to recover previously generated values. Periodically reseeding would prevent 1) the output to actually repeat, but given the large internal state, this should not be a concern for common usage; 2) recovering internal state from leaked bits in PRNG's output; 3) recovering previously generated value due to full internal state leak through unknown mean. Also this CSPRNG must not be used to generate a string of random bytes larger than 256GBytes with a single call futils_random_bytes(), due to internal counter rollover. Doing otherwise would make the generated string repeat each 256GBytes. When the generated data must be kept secret and not be recoverable through internal state leak, a key for example, the strong implementation is required, so this patch makes it available as futils_random_strong(). It *must* be used when unpredictability is required for the security to be resistant to an attacker. BEWARE BEWARE BEWARE BEWARE BEWARE BEWARE BEWARE BEWARE
- Loading branch information