mirror of https://github.com/golang/go.git
crypto/internal/fips/drbg: implement CTR_DRBG
For #69536 Change-Id: I016bb723841acbda50f013db46f9d2dda200e1fd Reviewed-on: https://go-review.googlesource.com/c/go/+/624977 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Roland Shoemaker <roland@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Auto-Submit: Filippo Valsorda <filippo@golang.org>
This commit is contained in:
parent
644628536f
commit
e67037fb27
|
|
@ -40,6 +40,18 @@ func (c *CTR) XORKeyStream(dst, src []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
// RoundToBlock is used by CTR_DRBG, which discards the rightmost unused bits at
|
||||
// each request. It rounds the offset up to the next block boundary.
|
||||
func RoundToBlock(c *CTR) {
|
||||
if remainder := c.offset % BlockSize; remainder != 0 {
|
||||
var carry uint64
|
||||
c.offset, carry = bits.Add64(c.offset, BlockSize-remainder, 0)
|
||||
if carry != 0 {
|
||||
panic("crypto/aes: counter overflow")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XORKeyStreamAt behaves like XORKeyStream but keeps no state, and instead
|
||||
// seeks into the keystream by the given bytes offset from the start (ignoring
|
||||
// any XORKetStream calls). This allows for random access into the keystream, up
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
// 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
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// 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
|
||||
}
|
||||
|
|
@ -452,6 +452,7 @@ var depsRules = `
|
|||
< crypto/internal/fips/alias
|
||||
< crypto/internal/fips/subtle
|
||||
< crypto/internal/fips/aes
|
||||
< crypto/internal/fips/drbg
|
||||
< crypto/internal/fips/sha256
|
||||
< crypto/internal/fips/sha512
|
||||
< crypto/internal/fips/sha3
|
||||
|
|
|
|||
Loading…
Reference in New Issue