ctrdrbg.mx raw

   1  // Copyright 2024 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package drbg
   6  
   7  import (
   8  	"crypto/internal/fips140"
   9  	"crypto/internal/fips140/aes"
  10  	"crypto/internal/fips140/subtle"
  11  	"crypto/internal/fips140deps/byteorder"
  12  	"math/bits"
  13  )
  14  
  15  // Counter is an SP 800-90A Rev. 1 CTR_DRBG instantiated with AES-256.
  16  //
  17  // Per Table 3, it has a security strength of 256 bits, a seed size of 384 bits,
  18  // a counter length of 128 bits, a reseed interval of 2^48 requests, and a
  19  // maximum request size of 2^19 bits (2^16 bytes, 64 KiB).
  20  //
  21  // We support a narrow range of parameters that fit the needs of our RNG:
  22  // AES-256, no derivation function, no personalization string, no prediction
  23  // resistance, and 384-bit additional input.
  24  //
  25  // WARNING: this type provides tightly scoped support for the DRBG
  26  // functionality we need for FIPS 140-3 _only_. This type _should not_ be used
  27  // outside of the FIPS 140-3 module for any other use.
  28  //
  29  // In particular, as documented, Counter does not support the derivation
  30  // function, or personalization strings which are necessary for safely using
  31  // this DRBG for generic purposes without leaking sensitive values.
  32  type Counter struct {
  33  	// c is instantiated with K as the key and V as the counter.
  34  	c aes.CTR
  35  
  36  	reseedCounter uint64
  37  }
  38  
  39  const (
  40  	keySize        = 256 / 8
  41  	SeedSize       = keySize + aes.BlockSize
  42  	reseedInterval = 1 << 48
  43  	maxRequestSize = (1 << 19) / 8
  44  )
  45  
  46  func NewCounter(entropy *[SeedSize]byte) *Counter {
  47  	// CTR_DRBG_Instantiate_algorithm, per Section 10.2.1.3.1.
  48  	fips140.RecordApproved()
  49  
  50  	K := []byte{:keySize}
  51  	V := []byte{:aes.BlockSize}
  52  
  53  	// V starts at 0, but is incremented in CTR_DRBG_Update before each use,
  54  	// unlike AES-CTR where it is incremented after each use.
  55  	V[len(V)-1] = 1
  56  
  57  	cipher, err := aes.New(K)
  58  	if err != nil {
  59  		panic(err)
  60  	}
  61  
  62  	c := &Counter{}
  63  	c.c = *aes.NewCTR(cipher, V)
  64  	c.update(entropy)
  65  	c.reseedCounter = 1
  66  	return c
  67  }
  68  
  69  func (c *Counter) update(seed *[SeedSize]byte) {
  70  	// CTR_DRBG_Update, per Section 10.2.1.2.
  71  
  72  	temp := []byte{:SeedSize}
  73  	c.c.XORKeyStream(temp, seed[:])
  74  	K := temp[:keySize]
  75  	V := temp[keySize:]
  76  
  77  	// Again, we pre-increment V, like in NewCounter.
  78  	increment((*[aes.BlockSize]byte)(V))
  79  
  80  	cipher, err := aes.New(K)
  81  	if err != nil {
  82  		panic(err)
  83  	}
  84  	c.c = *aes.NewCTR(cipher, V)
  85  }
  86  
  87  func increment(v *[aes.BlockSize]byte) {
  88  	hi := byteorder.BEUint64(v[:8])
  89  	lo := byteorder.BEUint64(v[8:])
  90  	lo, c := bits.Add64(lo, 1, 0)
  91  	hi, _ = bits.Add64(hi, 0, c)
  92  	byteorder.BEPutUint64(v[:8], hi)
  93  	byteorder.BEPutUint64(v[8:], lo)
  94  }
  95  
  96  func (c *Counter) Reseed(entropy, additionalInput *[SeedSize]byte) {
  97  	// CTR_DRBG_Reseed_algorithm, per Section 10.2.1.4.1.
  98  	fips140.RecordApproved()
  99  
 100  	var seed [SeedSize]byte
 101  	subtle.XORBytes(seed[:], entropy[:], additionalInput[:])
 102  	c.update(&seed)
 103  	c.reseedCounter = 1
 104  }
 105  
 106  // Generate produces at most maxRequestSize bytes of random data in out.
 107  func (c *Counter) Generate(out []byte, additionalInput *[SeedSize]byte) (reseedRequired bool) {
 108  	// CTR_DRBG_Generate_algorithm, per Section 10.2.1.5.1.
 109  	fips140.RecordApproved()
 110  
 111  	if len(out) > maxRequestSize {
 112  		panic("crypto/drbg: internal error: request size exceeds maximum")
 113  	}
 114  
 115  	// Step 1.
 116  	if c.reseedCounter > reseedInterval {
 117  		return true
 118  	}
 119  
 120  	// Step 2.
 121  	if additionalInput != nil {
 122  		c.update(additionalInput)
 123  	} else {
 124  		// If the additional input is null, the first CTR_DRBG_Update is
 125  		// skipped, but the additional input is replaced with an all-zero string
 126  		// for the second CTR_DRBG_Update.
 127  		additionalInput = &[SeedSize]byte{}
 128  	}
 129  
 130  	// Steps 3-5.
 131  	clear(out)
 132  	c.c.XORKeyStream(out, out)
 133  	aes.RoundToBlock(&c.c)
 134  
 135  	// Step 6.
 136  	c.update(additionalInput)
 137  
 138  	// Step 7.
 139  	c.reseedCounter++
 140  
 141  	// Step 8.
 142  	return false
 143  }
 144