77 lines
2.4 KiB
Go
77 lines
2.4 KiB
Go
// Package randstream provides a fast, unbounded stream of pseudorandom
|
|
// bytes as an io.Reader. It backs `sutil generate random`.
|
|
//
|
|
// It exists because reading /dev/urandom is slow: operating system random
|
|
// devices are built for small requests of key material, not for filling
|
|
// disks or producing gigabytes of test data. A Reader instead expands a
|
|
// 32-byte key with the ChaCha8 generator from math/rand/v2, which runs at
|
|
// close to memory bandwidth and never blocks.
|
|
//
|
|
// When Params.Seed is set, the key is SHA-256 of the seed string and the
|
|
// stream is fully deterministic: the same seed always produces the same
|
|
// bytes, which makes test data reproducible. When Seed is empty, the key
|
|
// is read from crypto/rand (the operating system's secure random source,
|
|
// /dev/urandom or its equivalent) and the stream is unpredictable.
|
|
//
|
|
// The output is not meant to be used as cryptographic key material; use
|
|
// crypto/rand for that.
|
|
package randstream
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
mathrand "math/rand/v2"
|
|
)
|
|
|
|
// KeySize is the size in bytes of the key that seeds the stream.
|
|
const KeySize = 32
|
|
|
|
// Params configures New.
|
|
type Params struct {
|
|
// Seed, when non-empty, makes the stream deterministic: the same seed
|
|
// always yields the same byte sequence. When empty, the stream is
|
|
// keyed from crypto/rand and differs on every call to New.
|
|
Seed string
|
|
}
|
|
|
|
// Reader is an unbounded stream of pseudorandom bytes. Read never returns
|
|
// an error and never returns fewer bytes than requested. A Reader is not
|
|
// safe for concurrent use.
|
|
type Reader struct {
|
|
generator *mathrand.ChaCha8
|
|
}
|
|
|
|
// New returns a Reader configured by p.
|
|
func New(p Params) (*Reader, error) {
|
|
key, err := keyFromParams(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Reader{generator: mathrand.NewChaCha8(key)}, nil
|
|
}
|
|
|
|
// Read fills b with pseudorandom bytes. It always returns len(b) and a nil
|
|
// error.
|
|
func (r *Reader) Read(b []byte) (int, error) {
|
|
return r.generator.Read(b)
|
|
}
|
|
|
|
// keyFromParams derives the ChaCha8 key: SHA-256 of the seed string, or
|
|
// fresh bytes from crypto/rand when no seed is given.
|
|
func keyFromParams(p Params) ([KeySize]byte, error) {
|
|
if p.Seed != "" {
|
|
return sha256.Sum256([]byte(p.Seed)), nil
|
|
}
|
|
|
|
var key [KeySize]byte
|
|
|
|
_, err := rand.Read(key[:])
|
|
if err != nil {
|
|
return key, fmt.Errorf("randstream: reading key from crypto/rand: %w", err)
|
|
}
|
|
|
|
return key, nil
|
|
}
|