crypto/internal/fips/aes/gcm: add CMAC

Change-Id: I5602dbf485c5c8a221e71c79961588e33f90452d
Reviewed-on: https://go-review.googlesource.com/c/go/+/626435
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
This commit is contained in:
Filippo Valsorda 2024-11-06 14:03:58 +01:00 committed by Gopher Robot
parent 7d7618971e
commit 7cc488c8b5
2 changed files with 153 additions and 0 deletions

View File

@ -0,0 +1,77 @@
// 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 gcm
import (
"crypto/internal/fips"
"crypto/internal/fips/aes"
"crypto/internal/fips/subtle"
)
// CMAC implements the CMAC mode from NIST SP 800-38B.
//
// It is optimized for use in Counter KDF (SP 800-108r1) and XAES-256-GCM
// (https://c2sp.org/XAES-256-GCM), rather than for exposing it to applications
// as a stand-alone MAC.
type CMAC struct {
b aes.Block
k1 [aes.BlockSize]byte
k2 [aes.BlockSize]byte
}
func NewCMAC(b *aes.Block) *CMAC {
c := &CMAC{b: *b}
c.deriveSubkeys()
return c
}
func (c *CMAC) deriveSubkeys() {
c.b.Encrypt(c.k1[:], c.k1[:])
msb := shiftLeft(&c.k1)
c.k1[len(c.k1)-1] ^= msb * 0b10000111
c.k2 = c.k1
msb = shiftLeft(&c.k2)
c.k2[len(c.k2)-1] ^= msb * 0b10000111
}
func (c *CMAC) MAC(m []byte) [aes.BlockSize]byte {
fips.RecordApproved()
_ = c.b // Hoist the nil check out of the loop.
var x [aes.BlockSize]byte
if len(m) == 0 {
// Special-cased as a single empty partial final block.
x = c.k2
x[len(m)] ^= 0b10000000
c.b.Encrypt(x[:], x[:])
return x
}
for len(m) >= aes.BlockSize {
subtle.XORBytes(x[:], m[:aes.BlockSize], x[:])
if len(m) == aes.BlockSize {
// Final complete block.
subtle.XORBytes(x[:], c.k1[:], x[:])
}
c.b.Encrypt(x[:], x[:])
m = m[aes.BlockSize:]
}
if len(m) > 0 {
// Final incomplete block.
subtle.XORBytes(x[:], m, x[:])
subtle.XORBytes(x[:], c.k2[:], x[:])
x[len(m)] ^= 0b10000000
c.b.Encrypt(x[:], x[:])
}
return x
}
// shiftLeft sets x to x << 1, and returns MSB₁(x).
func shiftLeft(x *[aes.BlockSize]byte) byte {
var msb byte
for i := len(x) - 1; i >= 0; i-- {
msb, x[i] = x[i]>>7, x[i]<<1|msb
}
return msb
}

View File

@ -0,0 +1,76 @@
// 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 gcm_test
import (
"bytes"
"crypto/internal/cryptotest"
"crypto/internal/fips/aes"
"crypto/internal/fips/aes/gcm"
"encoding/hex"
"strings"
"testing"
)
var sink byte
func TestAllocations(t *testing.T) {
cryptotest.SkipTestAllocations(t)
if allocs := testing.AllocsPerRun(10, func() {
b, err := aes.New(make([]byte, 16))
if err != nil {
t.Fatal(err)
}
c := gcm.NewCMAC(b)
sink ^= c.MAC(make([]byte, 16))[0]
}); allocs > 0 {
t.Errorf("expected zero allocations, got %0.1f", allocs)
}
}
func TestCMAC(t *testing.T) {
// https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_CMAC.pdf
key := "2B7E1516 28AED2A6 ABF71588 09CF4F3C"
tests := []struct {
in, out string
}{
{
"",
"BB1D6929 E9593728 7FA37D12 9B756746",
},
{
"6BC1BEE2 2E409F96 E93D7E11 7393172A",
"070A16B4 6B4D4144 F79BDD9D D04A287C",
},
{
"6BC1BEE2 2E409F96 E93D7E11 7393172A AE2D8A57",
"7D85449E A6EA19C8 23A7BF78 837DFADE",
},
}
b, err := aes.New(decodeHex(t, key))
if err != nil {
t.Fatal(err)
}
c := gcm.NewCMAC(b)
for i, test := range tests {
in := decodeHex(t, test.in)
out := decodeHex(t, test.out)
got := c.MAC(in)
if !bytes.Equal(got[:], out) {
t.Errorf("test %d: got %x, want %x", i, got, out)
}
}
}
func decodeHex(t *testing.T, s string) []byte {
t.Helper()
s = strings.ReplaceAll(s, " ", "")
b, err := hex.DecodeString(s)
if err != nil {
t.Fatal(err)
}
return b
}