| // 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. |
| // |
| // WARNING: this type provides tightly scoped support for the DRBG |
| // functionality we need for FIPS 140-3 _only_. This type _should not_ be used |
| // outside of the FIPS 140-3 module for any other use. |
| // |
| // In particular, as documented, Counter does not support the derivation |
| // function, or personalization strings which are necessary for safely using |
| // this DRBG for generic purposes without leaking sensitive values. |
| 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 |
| } |