From: Jason A. Donenfeld Date: Mon, 3 Jan 2022 17:48:51 +0000 (+0100) Subject: crypto/rand: use fast key erasure RNG on plan9 instead of ANSI X9.31 X-Git-Tag: go1.19beta1~1166 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=46afa893ebf85e23dd820a11e6007a9adb503419;p=gostls13.git crypto/rand: use fast key erasure RNG on plan9 instead of ANSI X9.31 This should be a bit faster and slicker than the very old ANSI X9.31, which relied on the system time. Uses AES instead of ChaCha because it's in the standard library. Reference: https://blog.cr.yp.to/20170723-random.html Reference: https://github.com/jedisct1/supercop/blob/master/crypto_rng/aes256/ref/rng.c Change-Id: Ib7b37a83cca29f5d346355b7cb8cfe5250086b95 Reviewed-on: https://go-review.googlesource.com/c/go/+/375215 Trust: Jason Donenfeld Reviewed-by: Filippo Valsorda Run-TryBot: Filippo Valsorda Auto-Submit: Filippo Valsorda TryBot-Result: Gopher Robot Reviewed-by: Roland Shoemaker --- diff --git a/src/crypto/rand/rand_plan9.go b/src/crypto/rand/rand_plan9.go index b81d73ca80..5d0af0959e 100644 --- a/src/crypto/rand/rand_plan9.go +++ b/src/crypto/rand/rand_plan9.go @@ -9,12 +9,10 @@ package rand import ( "crypto/aes" - "crypto/cipher" "encoding/binary" "io" "os" "sync" - "sync/atomic" "time" ) @@ -27,83 +25,63 @@ func init() { // reader is a new pseudorandom generator that seeds itself by // reading from /dev/random. The Read method on the returned // reader always returns the full amount asked for, or else it -// returns an error. The generator uses the X9.31 algorithm with -// AES-128, reseeding after every 1 MB of generated data. +// returns an error. The generator is a fast key erasure RNG. type reader struct { - mu sync.Mutex - budget int // number of bytes that can be generated - cipher cipher.Block - entropy io.Reader - entropyUsed int32 // atomic; whether entropy has been used - time, seed, dst, key [aes.BlockSize]byte + mu sync.Mutex + seeded sync.Once + seedErr error + key [32]byte } -func warnBlocked() { - println("crypto/rand: blocked for 60 seconds waiting to read random data from the kernel") -} - -func (r *reader) readEntropy(b []byte) error { - if atomic.CompareAndSwapInt32(&r.entropyUsed, 0, 1) { - // First use of randomness. Start timer to warn about - // being blocked on entropy not being available. - t := time.AfterFunc(time.Minute, warnBlocked) +func (r *reader) Read(b []byte) (n int, err error) { + r.seeded.Do(func() { + t := time.AfterFunc(time.Minute, func() { + println("crypto/rand: blocked for 60 seconds waiting to read random data from the kernel") + }) defer t.Stop() - } - var err error - if r.entropy == nil { - r.entropy, err = os.Open(randomDevice) + entropy, err := os.Open(randomDevice) if err != nil { - return err + r.seedErr = err + return } + _, r.seedErr = io.ReadFull(entropy, r.key[:]) + }) + if r.seedErr != nil { + return 0, r.seedErr } - _, err = io.ReadFull(r.entropy, b) - return err -} -func (r *reader) Read(b []byte) (n int, err error) { r.mu.Lock() - defer r.mu.Unlock() - n = len(b) - - for len(b) > 0 { - if r.budget == 0 { - err = r.readEntropy(r.seed[0:]) - if err != nil { - return n - len(b), err - } - err = r.readEntropy(r.key[0:]) - if err != nil { - return n - len(b), err - } - r.cipher, err = aes.NewCipher(r.key[0:]) - if err != nil { - return n - len(b), err - } - r.budget = 1 << 20 // reseed after generating 1MB - } - r.budget -= aes.BlockSize - - // ANSI X9.31 (== X9.17) algorithm, but using AES in place of 3DES. - // - // single block: - // t = encrypt(time) - // dst = encrypt(t^seed) - // seed = encrypt(t^dst) - ns := time.Now().UnixNano() - binary.BigEndian.PutUint64(r.time[:], uint64(ns)) - r.cipher.Encrypt(r.time[0:], r.time[0:]) - for i := 0; i < aes.BlockSize; i++ { - r.dst[i] = r.time[i] ^ r.seed[i] - } - r.cipher.Encrypt(r.dst[0:], r.dst[0:]) - for i := 0; i < aes.BlockSize; i++ { - r.seed[i] = r.time[i] ^ r.dst[i] + blockCipher, err := aes.NewCipher(r.key[:]) + if err != nil { + r.mu.Unlock() + return 0, err + } + var ( + counter uint64 + block [aes.BlockSize]byte + ) + inc := func() { + counter++ + if counter == 0 { + panic("crypto/rand counter wrapped") } - r.cipher.Encrypt(r.seed[0:], r.seed[0:]) - - m := copy(b, r.dst[0:]) - b = b[m:] + binary.LittleEndian.PutUint64(block[:], counter) } + blockCipher.Encrypt(r.key[:aes.BlockSize], block[:]) + inc() + blockCipher.Encrypt(r.key[aes.BlockSize:], block[:]) + inc() + r.mu.Unlock() + n = len(b) + for len(b) >= aes.BlockSize { + blockCipher.Encrypt(b[:aes.BlockSize], block[:]) + inc() + b = b[aes.BlockSize:] + } + if len(b) > 0 { + blockCipher.Encrypt(block[:], block[:]) + copy(b, block[:]) + } return n, nil }