go get lukechampine.com/frand
frand is a fast-key-erasure
CSPRNG in userspace. Its output is
sourced from the keystream of a ChaCha
cipher, much like the CSPRNG in the Linux and BSD kernels. The initial cipher key is
derived from the kernel CSPRNG, after which no new entropy is ever mixed in.
The primary advantage of
crypto/rand is speed: when generating
large amounts of random data,
frand is 20x faster than
crypto/rand, and over
100x faster when generating data in parallel.
frand is also far more
convenient to use than
crypto/rand, as its methods cannot return errors, and
helper methods such as
Perm are provided. In short, it is as fast
and convenient as
math/rand, but far more secure.
frand closely follows the FKE-CSPRNG design linked above. The generator
maintains a buffer consisting of a ChaCha key followed by random data. When a
caller requests data, the generator fills the request from its buffer, and
immediately overwrites that portion of the buffer with zeros. If the buffer is
exhausted, the generator "refills" the buffer by xoring it with the ChaCha key's
keystream; thus, the key is only used once, and immediately overwrites itself.
This provides forward secrecy:
even if an attacker learns the secret key, they cannot learn what data was
previously output from the generator.
One optimization is implemented. If the caller requests a large amount of data (larger than the generator's buffer), the generator does not repeatedly fill and drain its buffer. Instead, it requests 32 bytes from itself, and uses this as a ChaCha key to directly generate the amount of data requested. The key is then overwritten with zeros.
The default generator uses ChaCha12. ChaCha8 is significantly weaker without being much faster; ChaCha20 is significantly slower without being much stronger.
init time, a "master" generator is created using an initial key sourced
crypto/rand. New generators source their initial keys from this master
generator. Following djb's recommendation,
the generator never mixes in new entropy.
Calls to global functions like
Intn are serviced by a
of generators derived from the master generator. This allows
frand to scale
near-linearly across CPU cores, which is what makes it so much faster than
math/rand in parallel benchmarks.
Some may object to substituting a userspace CSPRNG for the kernel's
CSPRNG. Certain userspace CSPRNGs (e.g. OpenSSL)
have contained bugs, causing serious vulnerabilities.
frand has some
advantages over these CSPRNGs: it never mixes in new entropy, it doesn't
maintain an "entropy estimate," and its implementation is extremely simple.
Still, if you only need to generate a handful of secret keys, you should
probably stick with
Even if you aren't comfortable using
frand for critical secrets, you should
still use it everywhere you would normally use an insecure PRNG like
math/rand. Go programmers frequently use
math/rand because it is faster and
more convenient than
crypto/rand, and only use
crypto/rand when "true"
randomness is required. This is an unfortunate state of affairs, because misuse
math/rand can lead to bugs or vulnerabilities. For example, using
math/rand to generate "random" test cases will produce identical coverage from
run-to-run if the generator is left unseeded. More seriously, using
to salt a hash table may make your program vulnerable to denial of service attacks.
math/rand would avoid both of these outcomes.
|Read (32b)||59 MB/s||215 MB/s||964 MB/s||634.21 MB/s|
|Read (32b, concurrent)||70 MB/s||615 MB/s||3566 MB/s||198.97 MB/s|
|Read (512kb)||239 MB/s||452 MB/s||5094 MB/s||965.85 MB/s|
|Read (512kb, concurrent)||191 MB/s||1599 MB/s||19665 MB/s||958.01 MB/s|
|Intn (n =~ 4e18)||725 ns/op||210 ns/op||45 ns/op||20 ns/op|
|BigIntn (n = 2^630)||1013 ns/op||468 ns/op||223 ns/op||295 ns/op|
|Perm (n = 32)||17197 ns/op||5021 ns/op||954 ns/op||789 ns/op|
"Concurrent" means the
Read function was called in parallel from 64 goroutines.
Intn was benchmarked with n = 2^62 + 1, which maximizes the number of expected
retries required to remove bias. The number of expected retries is 1.333.