// Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package drbg import ( "crypto/internal/fips140" "crypto/internal/fips140/aes" "crypto/internal/fips140/subtle" "crypto/internal/fips140deps/byteorder" "math/bits" ) // Counter is an SP 800-90A Rev. 1 CTR_DRBG instantiated with AES-256. // // Per Table 3, it has a security strength of 256 bits, a seed size of 384 bits, // a counter length of 128 bits, a reseed interval of 2^48 requests, and a // maximum request size of 2^19 bits (2^16 bytes, 64 KiB). // // We support a narrow range of parameters that fit the needs of our RNG: // AES-256, no derivation function, no personalization string, no prediction // resistance, and 384-bit additional input. type Counter struct { // c is instantiated with K as the key and V as the counter. c aes.CTR reseedCounter uint64 } const ( keySize = 256 / 8 SeedSize = keySize + aes.BlockSize reseedInterval = 1 << 48 maxRequestSize = (1 << 19) / 8 ) func NewCounter(entropy *[SeedSize]byte) *Counter { // CTR_DRBG_Instantiate_algorithm, per Section 10.2.1.3.1. fips140.RecordApproved() K := make([]byte, keySize) V := make([]byte, aes.BlockSize) // V starts at 0, but is incremented in CTR_DRBG_Update before each use, // unlike AES-CTR where it is incremented after each use. V[len(V)-1] = 1 cipher, err := aes.New(K) if err != nil { panic(err) } c := &Counter{} c.c = *aes.NewCTR(cipher, V) c.update(entropy) c.reseedCounter = 1 return c } func (c *Counter) update(seed *[SeedSize]byte) { // CTR_DRBG_Update, per Section 10.2.1.2. temp := make([]byte, SeedSize) c.c.XORKeyStream(temp, seed[:]) K := temp[:keySize] V := temp[keySize:] // Again, we pre-increment V, like in NewCounter. increment((*[aes.BlockSize]byte)(V)) cipher, err := aes.New(K) if err != nil { panic(err) } c.c = *aes.NewCTR(cipher, V) } func increment(v *[aes.BlockSize]byte) { hi := byteorder.BEUint64(v[:8]) lo := byteorder.BEUint64(v[8:]) lo, c := bits.Add64(lo, 1, 0) hi, _ = bits.Add64(hi, 0, c) byteorder.BEPutUint64(v[:8], hi) byteorder.BEPutUint64(v[8:], lo) } func (c *Counter) Reseed(entropy, additionalInput *[SeedSize]byte) { // CTR_DRBG_Reseed_algorithm, per Section 10.2.1.4.1. fips140.RecordApproved() var seed [SeedSize]byte subtle.XORBytes(seed[:], entropy[:], additionalInput[:]) c.update(&seed) c.reseedCounter = 1 } // Generate produces at most maxRequestSize bytes of random data in out. func (c *Counter) Generate(out []byte, additionalInput *[SeedSize]byte) (reseedRequired bool) { // CTR_DRBG_Generate_algorithm, per Section 10.2.1.5.1. fips140.RecordApproved() if len(out) > maxRequestSize { panic("crypto/drbg: internal error: request size exceeds maximum") } // Step 1. if c.reseedCounter > reseedInterval { return true } // Step 2. if additionalInput != nil { c.update(additionalInput) } else { // If the additional input is null, the first CTR_DRBG_Update is // skipped, but the additional input is replaced with an all-zero string // for the second CTR_DRBG_Update. additionalInput = new([SeedSize]byte) } // Steps 3-5. clear(out) c.c.XORKeyStream(out, out) aes.RoundToBlock(&c.c) // Step 6. c.update(additionalInput) // Step 7. c.reseedCounter++ // Step 8. return false }