--- /dev/null
+// 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/fips/aes"
+ "crypto/internal/fips/subtle"
+ "internal/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.
+
+ 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.
+ 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.
+
+ 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
+}
--- /dev/null
+// 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_test
+
+import (
+ "bytes"
+ "crypto/internal/fips/drbg"
+ "crypto/internal/fips/subtle"
+ "encoding/hex"
+ "testing"
+)
+
+func TestCounter(t *testing.T) {
+ // https://github.com/usnistgov/ACVP-Server/blob/fb44dce/gen-val/json-files/ctrDRBG-1.0/prompt.json#L4447-L4482
+
+ entropyInput := decodeHex("9FCBB4CCC0135C484BDED061DA9FD70748682FE84166B97FF53F9AA1909B2E95D3D529C0F453B3AC575D12AA441CC5CD")
+ persoString := decodeHex("2C9FED0B39556CDBE699EBCA2A0EC7EECB287E8744475050C572FA8AE9ED0A4A7D6F1CABF1C4278532FB20AF7D64BD32")
+ reseedEntropy := decodeHex("913C0DA19B010EDDD55A7A4F3F713EEF5B1534D34360A7EC376AE71A6B340043CC7726F762CB853453F399B3A645062A")
+ reseedAdditional := decodeHex("2D9D4EC141A22E6CD2F6EE4F6719CF6BDF95CFE50B8D5EA6C87D38B4B872706FFF80B0380BB90E9C42D11D6526E56C29")
+ additional1 := decodeHex("A642F06D327828F3E84564A3E37D60C157073B95864CA07981B0189668A0D978CD5DC68F06801CEFF0DC839A312B028E")
+ additional2 := decodeHex("9DB14BABFA9107C88BA92073C0B4A65E89147EA06D74B894142979482F452915B35B5636F9B8A951759735ADE7C8D5D1")
+ returnedBits := decodeHex("F10C645683FF0131254052ED4C698122B46B563654C29D728AC191CA4AAEFE649EEFE4C6FC33B25BB739294DD5CF578099F856C98D98000CBF971F1E6EA900822FF8C110118F6520471744D3F8A3F5C7D568494240E57F5488AF9C9F9F4E7322F56CCD843C0DBFCE9170C02E205389420527F23EDB3369D9FCC5E34901B5BA4EB71B973FC7982FFE0899FF7FE53EE0C4F51A3EF93EF9C6D4D279DD7536F8776BE94AAA05E89EF6E6AEE8832B4B42FFCA5FB91EC0273F9EF945865512889B0C5EE141D1B38DF827D2A694835561628C6F9B093A01A835F07ADBB9E03FEBF93389E8F3B86E1E0ABF1F9958FA286AD995289C2F606D1A9043A166C1AFE8D00769C712650819C9068A4BD22717C98338395A7BA6E95B5178BFBF4EFB0F05A91713BA8BF2127A6BA1EDFA6D1CAB05C03EE0D2AFE1DA4EB8F2C579EC872FF4B602027EF4BDCF2F4B01423F8E600A13D7CACB6AB83263BA58F907694AF614A6724FD0E4C627A0D91DDC6716C697FACE6F4808A4F37B731DE4E0CD4766CEADAAAF47992505299C72AC1A6E9A8335B8D7E501B3841188D0DA4DE5267674444DC2B0CF9F010756FA865A25CA3F1B24C34E845B2259926B6A867A7684DE68A6137C4FB0F47A2E54AE9E6455BEBA0B0A9629644FE9E378EE95386443BA977124FFD1192E9F460684C7B09FA99F5F93F04F56FD7955E042187887CE696F1934017E458B16B5C9")
+
+ // We don't support personalization strings, but the pre-generated JSON
+ // vectors always use them, so just pre-mix them.
+ var seed [drbg.SeedSize]byte
+ subtle.XORBytes(seed[:], entropyInput, persoString)
+ c := drbg.NewCounter(&seed)
+
+ c.Reseed((*[48]byte)(reseedEntropy), (*[48]byte)(reseedAdditional))
+
+ buf := make([]byte, len(returnedBits))
+ c.Generate(buf, (*[48]byte)(additional1))
+
+ c.Generate(buf, (*[48]byte)(additional2))
+ if !bytes.Equal(buf, returnedBits) {
+ t.Errorf("unexpected output:\n%x\n%x", buf, returnedBits)
+ }
+}
+
+func decodeHex(s string) []byte {
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ panic(err)
+ }
+ return b
+}