frand

package module
v1.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 15, 2021 License: MIT Imports: 7 Imported by: 127

README

frand

GoDoc Go Report Card

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 frand over 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 Intn and Perm are provided. In short, it is as fast and convenient as math/rand, but far more secure.

Design

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.

At init time, a "master" generator is created using an initial key sourced from 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 Read and Intn are serviced by a sync.Pool 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 crypto/rand and math/rand in parallel benchmarks.

Security

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

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 of 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 math/rand to salt a hash table may make your program vulnerable to denial of service attacks. Substituting frand for math/rand would avoid both of these outcomes.

Benchmarks

Benchmark frand crypto/rand math/rand (insecure) fastrand
Read (32b) 964 MB/s 59 MB/s 634.21 MB/s 215 MB/s
Read (32b, concurrent) 3566 MB/s 70 MB/s 198.97 MB/s 615 MB/s
Read (512kb) 5094 MB/s 239 MB/s 965.85 MB/s 452 MB/s
Read (512kb, concurrent) 19665 MB/s 191 MB/s 958.01 MB/s 1599 MB/s
Intn (n =~ 4e18) 45 ns/op 725 ns/op 20 ns/op 210 ns/op
BigIntn (n = 2^630) 223 ns/op 1013 ns/op 295 ns/op 468 ns/op
Perm (n = 32) 954 ns/op 17197 ns/op 789 ns/op 5021 ns/op

Benchmark details:

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

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Reader rngReader

Reader is a global, shared instance of a cryptographically strong pseudo- random generator. Reader is safe for concurrent use by multiple goroutines.

Functions

func BigIntn

func BigIntn(n *big.Int) *big.Int

BigIntn returns a uniform random *big.Int in [0,n). It panics if n <= 0.

func Bytes

func Bytes(n int) []byte

Bytes is a helper function that allocates and returns n bytes of random data.

func Entropy128 added in v1.3.0

func Entropy128() [16]byte

Entropy128 is a helper function that returns 128 bits of random data.

func Entropy192 added in v1.3.0

func Entropy192() [24]byte

Entropy192 is a helper function that returns 192 bits of random data.

func Entropy256 added in v1.3.0

func Entropy256() [32]byte

Entropy256 is a helper function that returns 256 bits of random data.

func Float64 added in v1.1.0

func Float64() float64

Float64 returns a random float64 in [0,1).

func Intn

func Intn(n int) int

Intn returns a uniform random int in [0,n). It panics if n <= 0.

func Perm

func Perm(n int) []int

Perm returns a random permutation of the integers [0,n). It panics if n < 0.

func Read

func Read(b []byte) (int, error)

Read fills b with random data. It always returns len(b), nil.

func Shuffle added in v1.2.0

func Shuffle(n int, swap func(i, j int))

Shuffle randomly permutes n elements by repeatedly calling swap in the range [0,n). It panics if n < 0.

func Uint64n

func Uint64n(n uint64) uint64

Uint64n returns a uniform random uint64 in [0,n). It panics if n == 0.

Types

type RNG

type RNG struct {
	// contains filtered or unexported fields
}

An RNG is a cryptographically-strong RNG constructed from the ChaCha stream cipher.

func New

func New() *RNG

New returns a new RNG instance. The instance is seeded with entropy from crypto/rand, albeit indirectly; its seed is generated by a global "master" RNG, which itself is seeded from crypto/rand. This means the frand package only reads system entropy once, at startup.

func NewCustom

func NewCustom(seed []byte, bufsize int, rounds int) *RNG

NewCustom returns a new RNG instance seeded with the provided entropy and using the specified buffer size and number of ChaCha rounds. It panics if len(seed) != 32, bufsize < 32, or rounds != 8, 12 or 20.

func (*RNG) BigIntn

func (r *RNG) BigIntn(n *big.Int) *big.Int

BigIntn returns a uniform random *big.Int in [0,n). It panics if n <= 0.

func (*RNG) Bytes

func (r *RNG) Bytes(n int) []byte

Bytes is a helper function that allocates and returns n bytes of random data.

func (*RNG) Entropy128 added in v1.3.0

func (r *RNG) Entropy128() (entropy [16]byte)

Entropy128 is a helper function that returns 128 bits of random data.

func (*RNG) Entropy192 added in v1.3.0

func (r *RNG) Entropy192() (entropy [24]byte)

Entropy192 is a helper function that returns 192 bits of random data.

func (*RNG) Entropy256 added in v1.3.0

func (r *RNG) Entropy256() (entropy [32]byte)

Entropy256 is a helper function that returns 256 bits of random data.

func (*RNG) Float64 added in v1.1.0

func (r *RNG) Float64() float64

Float64 returns a random float64 in [0,1).

func (*RNG) Intn

func (r *RNG) Intn(n int) int

Intn returns a uniform random int in [0,n). It panics if n <= 0.

func (*RNG) Perm

func (r *RNG) Perm(n int) []int

Perm returns a random permutation of the integers [0,n). It panics if n < 0.

func (*RNG) Read

func (r *RNG) Read(b []byte) (int, error)

Read fills b with random data. It always returns len(b), nil.

For performance reasons, calling Read once on a "large" buffer (larger than the RNG's internal buffer) will produce different output than calling Read multiple times on smaller buffers. If deterministic output is required, clients should call Read in a loop; when copying to an io.Writer, use io.CopyBuffer instead of io.Copy. Callers should also be aware that b is xored with random data, not directly overwritten; this means that the new contents of b depend on its previous contents.

func (*RNG) Shuffle added in v1.2.0

func (r *RNG) Shuffle(n int, swap func(i, j int))

Shuffle randomly permutes n elements by repeatedly calling swap in the range [0,n). It panics if n < 0.

func (*RNG) Uint64n

func (r *RNG) Uint64n(n uint64) uint64

Uint64n returns a uniform random uint64 in [0,n). It panics if n == 0.

type Source added in v1.4.0

type Source struct {
	// contains filtered or unexported fields
}

A Source is a math/rand-compatible source of entropy. It is safe for concurrent use by multiple goroutines

func NewSource added in v1.4.0

func NewSource() *Source

NewSource returns a source for use with the math/rand package.

func (*Source) Int63 added in v1.4.0

func (s *Source) Int63() int64

Int63 returns a non-negative random 63-bit integer as an int64.

func (*Source) Seed added in v1.4.0

func (s *Source) Seed(i int64)

Seed uses the provided seed value to initialize the Source to a deterministic state.

func (*Source) Uint64 added in v1.4.0

func (s *Source) Uint64() uint64

Uint64 returns a random 64-bit integer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL