]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/rand: use fast key erasure RNG on plan9 instead of ANSI X9.31
authorJason A. Donenfeld <Jason@zx2c4.com>
Mon, 3 Jan 2022 17:48:51 +0000 (18:48 +0100)
committerGopher Robot <gobot@golang.org>
Fri, 4 Mar 2022 15:48:03 +0000 (15:48 +0000)
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 <Jason@zx2c4.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
src/crypto/rand/rand_plan9.go

index b81d73ca803ae4e62c0e9374861d7de07ca01aed..5d0af0959e9df5afc191c47c2a8a52957ac1997e 100644 (file)
@@ -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
 }