Source file src/crypto/internal/fips140/drbg/ctrdrbg.go

     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  type Counter struct {
    25  	// c is instantiated with K as the key and V as the counter.
    26  	c aes.CTR
    27  
    28  	reseedCounter uint64
    29  }
    30  
    31  const (
    32  	keySize        = 256 / 8
    33  	SeedSize       = keySize + aes.BlockSize
    34  	reseedInterval = 1 << 48
    35  	maxRequestSize = (1 << 19) / 8
    36  )
    37  
    38  func NewCounter(entropy *[SeedSize]byte) *Counter {
    39  	// CTR_DRBG_Instantiate_algorithm, per Section 10.2.1.3.1.
    40  	fips140.RecordApproved()
    41  
    42  	K := make([]byte, keySize)
    43  	V := make([]byte, aes.BlockSize)
    44  
    45  	// V starts at 0, but is incremented in CTR_DRBG_Update before each use,
    46  	// unlike AES-CTR where it is incremented after each use.
    47  	V[len(V)-1] = 1
    48  
    49  	cipher, err := aes.New(K)
    50  	if err != nil {
    51  		panic(err)
    52  	}
    53  
    54  	c := &Counter{}
    55  	c.c = *aes.NewCTR(cipher, V)
    56  	c.update(entropy)
    57  	c.reseedCounter = 1
    58  	return c
    59  }
    60  
    61  func (c *Counter) update(seed *[SeedSize]byte) {
    62  	// CTR_DRBG_Update, per Section 10.2.1.2.
    63  
    64  	temp := make([]byte, SeedSize)
    65  	c.c.XORKeyStream(temp, seed[:])
    66  	K := temp[:keySize]
    67  	V := temp[keySize:]
    68  
    69  	// Again, we pre-increment V, like in NewCounter.
    70  	increment((*[aes.BlockSize]byte)(V))
    71  
    72  	cipher, err := aes.New(K)
    73  	if err != nil {
    74  		panic(err)
    75  	}
    76  	c.c = *aes.NewCTR(cipher, V)
    77  }
    78  
    79  func increment(v *[aes.BlockSize]byte) {
    80  	hi := byteorder.BEUint64(v[:8])
    81  	lo := byteorder.BEUint64(v[8:])
    82  	lo, c := bits.Add64(lo, 1, 0)
    83  	hi, _ = bits.Add64(hi, 0, c)
    84  	byteorder.BEPutUint64(v[:8], hi)
    85  	byteorder.BEPutUint64(v[8:], lo)
    86  }
    87  
    88  func (c *Counter) Reseed(entropy, additionalInput *[SeedSize]byte) {
    89  	// CTR_DRBG_Reseed_algorithm, per Section 10.2.1.4.1.
    90  	fips140.RecordApproved()
    91  
    92  	var seed [SeedSize]byte
    93  	subtle.XORBytes(seed[:], entropy[:], additionalInput[:])
    94  	c.update(&seed)
    95  	c.reseedCounter = 1
    96  }
    97  
    98  // Generate produces at most maxRequestSize bytes of random data in out.
    99  func (c *Counter) Generate(out []byte, additionalInput *[SeedSize]byte) (reseedRequired bool) {
   100  	// CTR_DRBG_Generate_algorithm, per Section 10.2.1.5.1.
   101  	fips140.RecordApproved()
   102  
   103  	if len(out) > maxRequestSize {
   104  		panic("crypto/drbg: internal error: request size exceeds maximum")
   105  	}
   106  
   107  	// Step 1.
   108  	if c.reseedCounter > reseedInterval {
   109  		return true
   110  	}
   111  
   112  	// Step 2.
   113  	if additionalInput != nil {
   114  		c.update(additionalInput)
   115  	} else {
   116  		// If the additional input is null, the first CTR_DRBG_Update is
   117  		// skipped, but the additional input is replaced with an all-zero string
   118  		// for the second CTR_DRBG_Update.
   119  		additionalInput = new([SeedSize]byte)
   120  	}
   121  
   122  	// Steps 3-5.
   123  	clear(out)
   124  	c.c.XORKeyStream(out, out)
   125  	aes.RoundToBlock(&c.c)
   126  
   127  	// Step 6.
   128  	c.update(additionalInput)
   129  
   130  	// Step 7.
   131  	c.reseedCounter++
   132  
   133  	// Step 8.
   134  	return false
   135  }
   136  

View as plain text