Skip to content

Commit ae9387d

Browse files
committed
crypto/hmac: move implementation to crypto/internal/fips
For golang#69536 Change-Id: I38508a8de4ac321554a2c12ac70bcf9e25fad1aa
1 parent 2adc940 commit ae9387d

File tree

4 files changed

+211
-130
lines changed

4 files changed

+211
-130
lines changed

src/crypto/hmac/hmac.go

+3-129
Original file line numberDiff line numberDiff line change
@@ -23,105 +23,13 @@ package hmac
2323

2424
import (
2525
"crypto/internal/boring"
26+
"crypto/internal/fips/hmac"
2627
"crypto/subtle"
2728
"hash"
2829
)
2930

30-
// FIPS 198-1:
31-
// https://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf
32-
33-
// key is zero padded to the block size of the hash function
34-
// ipad = 0x36 byte repeated for key length
35-
// opad = 0x5c byte repeated for key length
36-
// hmac = H([key ^ opad] H([key ^ ipad] text))
37-
38-
// marshalable is the combination of encoding.BinaryMarshaler and
39-
// encoding.BinaryUnmarshaler. Their method definitions are repeated here to
40-
// avoid a dependency on the encoding package.
41-
type marshalable interface {
42-
MarshalBinary() ([]byte, error)
43-
UnmarshalBinary([]byte) error
44-
}
45-
46-
type hmac struct {
47-
opad, ipad []byte
48-
outer, inner hash.Hash
49-
50-
// If marshaled is true, then opad and ipad do not contain a padded
51-
// copy of the key, but rather the marshaled state of outer/inner after
52-
// opad/ipad has been fed into it.
53-
marshaled bool
54-
}
55-
56-
func (h *hmac) Sum(in []byte) []byte {
57-
origLen := len(in)
58-
in = h.inner.Sum(in)
59-
60-
if h.marshaled {
61-
if err := h.outer.(marshalable).UnmarshalBinary(h.opad); err != nil {
62-
panic(err)
63-
}
64-
} else {
65-
h.outer.Reset()
66-
h.outer.Write(h.opad)
67-
}
68-
h.outer.Write(in[origLen:])
69-
return h.outer.Sum(in[:origLen])
70-
}
71-
72-
func (h *hmac) Write(p []byte) (n int, err error) {
73-
return h.inner.Write(p)
74-
}
75-
76-
func (h *hmac) Size() int { return h.outer.Size() }
77-
func (h *hmac) BlockSize() int { return h.inner.BlockSize() }
78-
79-
func (h *hmac) Reset() {
80-
if h.marshaled {
81-
if err := h.inner.(marshalable).UnmarshalBinary(h.ipad); err != nil {
82-
panic(err)
83-
}
84-
return
85-
}
86-
87-
h.inner.Reset()
88-
h.inner.Write(h.ipad)
89-
90-
// If the underlying hash is marshalable, we can save some time by
91-
// saving a copy of the hash state now, and restoring it on future
92-
// calls to Reset and Sum instead of writing ipad/opad every time.
93-
//
94-
// If either hash is unmarshalable for whatever reason,
95-
// it's safe to bail out here.
96-
marshalableInner, innerOK := h.inner.(marshalable)
97-
if !innerOK {
98-
return
99-
}
100-
marshalableOuter, outerOK := h.outer.(marshalable)
101-
if !outerOK {
102-
return
103-
}
104-
105-
imarshal, err := marshalableInner.MarshalBinary()
106-
if err != nil {
107-
return
108-
}
109-
110-
h.outer.Reset()
111-
h.outer.Write(h.opad)
112-
omarshal, err := marshalableOuter.MarshalBinary()
113-
if err != nil {
114-
return
115-
}
116-
117-
// Marshaling succeeded; save the marshaled state for later
118-
h.ipad = imarshal
119-
h.opad = omarshal
120-
h.marshaled = true
121-
}
122-
12331
// New returns a new HMAC hash using the given [hash.Hash] type and key.
124-
// New functions like sha256.New from [crypto/sha256] can be used as h.
32+
// New functions like [crypto/sha256.New] can be used as h.
12533
// h must return a new Hash every time it is called.
12634
// Note that unlike other hash implementations in the standard library,
12735
// the returned Hash does not implement [encoding.BinaryMarshaler]
@@ -134,41 +42,7 @@ func New(h func() hash.Hash, key []byte) hash.Hash {
13442
}
13543
// BoringCrypto did not recognize h, so fall through to standard Go code.
13644
}
137-
hm := new(hmac)
138-
hm.outer = h()
139-
hm.inner = h()
140-
unique := true
141-
func() {
142-
defer func() {
143-
// The comparison might panic if the underlying types are not comparable.
144-
_ = recover()
145-
}()
146-
if hm.outer == hm.inner {
147-
unique = false
148-
}
149-
}()
150-
if !unique {
151-
panic("crypto/hmac: hash generation function does not produce unique values")
152-
}
153-
blocksize := hm.inner.BlockSize()
154-
hm.ipad = make([]byte, blocksize)
155-
hm.opad = make([]byte, blocksize)
156-
if len(key) > blocksize {
157-
// If key is too big, hash it.
158-
hm.outer.Write(key)
159-
key = hm.outer.Sum(nil)
160-
}
161-
copy(hm.ipad, key)
162-
copy(hm.opad, key)
163-
for i := range hm.ipad {
164-
hm.ipad[i] ^= 0x36
165-
}
166-
for i := range hm.opad {
167-
hm.opad[i] ^= 0x5c
168-
}
169-
hm.inner.Write(hm.ipad)
170-
171-
return hm
45+
return hmac.New(h(), h(), key)
17246
}
17347

17448
// Equal compares two MACs for equality without leaking timing information.

src/crypto/internal/fips/hash.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package fips
6+
7+
import "io"
8+
9+
// Hash is the common interface implemented by all hash functions. It is a copy
10+
// of [hash.Hash] from the standard library, to avoid depending on security
11+
// definitions from outside of the module.
12+
type Hash interface {
13+
// Write (via the embedded io.Writer interface) adds more data to the
14+
// running hash. It never returns an error.
15+
io.Writer
16+
17+
// Sum appends the current hash to b and returns the resulting slice.
18+
// It does not change the underlying hash state.
19+
Sum(b []byte) []byte
20+
21+
// Reset resets the Hash to its initial state.
22+
Reset()
23+
24+
// Size returns the number of bytes Sum will return.
25+
Size() int
26+
27+
// BlockSize returns the hash's underlying block size.
28+
// The Write method must be able to accept any amount
29+
// of data, but it may operate more efficiently if all writes
30+
// are a multiple of the block size.
31+
BlockSize() int
32+
}

src/crypto/internal/fips/hmac/hmac.go

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright 2009 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package hmac implements HMAC according to [FIPS 198-1].
6+
//
7+
// [FIPS 198-1]: https://doi.org/10.6028/NIST.FIPS.198-1
8+
package hmac
9+
10+
import (
11+
"crypto/internal/fips"
12+
"crypto/internal/fips/sha256"
13+
"crypto/internal/fips/sha512"
14+
)
15+
16+
// key is zero padded to the block size of the hash function
17+
// ipad = 0x36 byte repeated for key length
18+
// opad = 0x5c byte repeated for key length
19+
// hmac = H([key ^ opad] H([key ^ ipad] text))
20+
21+
// marshalable is the combination of encoding.BinaryMarshaler and
22+
// encoding.BinaryUnmarshaler. Their method definitions are repeated here to
23+
// avoid a dependency on the encoding package.
24+
type marshalable interface {
25+
MarshalBinary() ([]byte, error)
26+
UnmarshalBinary([]byte) error
27+
}
28+
29+
type HMAC struct {
30+
opad, ipad []byte
31+
outer, inner fips.Hash
32+
33+
// If marshaled is true, then opad and ipad do not contain a padded
34+
// copy of the key, but rather the marshaled state of outer/inner after
35+
// opad/ipad has been fed into it.
36+
marshaled bool
37+
}
38+
39+
func (h *HMAC) Sum(in []byte) []byte {
40+
origLen := len(in)
41+
in = h.inner.Sum(in)
42+
43+
if h.marshaled {
44+
if err := h.outer.(marshalable).UnmarshalBinary(h.opad); err != nil {
45+
panic(err)
46+
}
47+
} else {
48+
h.outer.Reset()
49+
h.outer.Write(h.opad)
50+
}
51+
h.outer.Write(in[origLen:])
52+
return h.outer.Sum(in[:origLen])
53+
}
54+
55+
func (h *HMAC) Write(p []byte) (n int, err error) {
56+
return h.inner.Write(p)
57+
}
58+
59+
func (h *HMAC) Size() int { return h.outer.Size() }
60+
func (h *HMAC) BlockSize() int { return h.inner.BlockSize() }
61+
62+
func (h *HMAC) Reset() {
63+
if h.marshaled {
64+
if err := h.inner.(marshalable).UnmarshalBinary(h.ipad); err != nil {
65+
panic(err)
66+
}
67+
return
68+
}
69+
70+
h.inner.Reset()
71+
h.inner.Write(h.ipad)
72+
73+
// If the underlying hash is marshalable, we can save some time by saving a
74+
// copy of the hash state now, and restoring it on future calls to Reset and
75+
// Sum instead of writing ipad/opad every time.
76+
//
77+
// We do this on Reset to avoid slowing down the common single-use case.
78+
//
79+
// This is allowed by FIPS 198-1, Section 6: "Conceptually, the intermediate
80+
// results of the compression function on the B-byte blocks (K0 ⊕ ipad) and
81+
// (K0 ⊕ opad) can be precomputed once, at the time of generation of the key
82+
// K, or before its first use. These intermediate results can be stored and
83+
// then used to initialize H each time that a message needs to be
84+
// authenticated using the same key. [...] These stored intermediate values
85+
// shall be treated and protected in the same manner as secret keys."
86+
marshalableInner, innerOK := h.inner.(marshalable)
87+
if !innerOK {
88+
return
89+
}
90+
marshalableOuter, outerOK := h.outer.(marshalable)
91+
if !outerOK {
92+
return
93+
}
94+
95+
imarshal, err := marshalableInner.MarshalBinary()
96+
if err != nil {
97+
return
98+
}
99+
100+
h.outer.Reset()
101+
h.outer.Write(h.opad)
102+
omarshal, err := marshalableOuter.MarshalBinary()
103+
if err != nil {
104+
return
105+
}
106+
107+
// Marshaling succeeded; save the marshaled state for later
108+
h.ipad = imarshal
109+
h.opad = omarshal
110+
h.marshaled = true
111+
}
112+
113+
// New returns a new HMAC hash using the given [fips.Hash] type and key.
114+
// h1 and h2 must be different instantiations of the same hash function,
115+
// with no writes having been made to either.
116+
func New(h1, h2 fips.Hash, key []byte) *HMAC {
117+
setServiceIndicator(h1, h2, key)
118+
119+
hm := new(HMAC)
120+
hm.outer = h1
121+
hm.inner = h2
122+
unique := true
123+
func() {
124+
defer func() {
125+
// The comparison might panic if the underlying types are not comparable.
126+
_ = recover()
127+
}()
128+
if hm.outer == hm.inner {
129+
unique = false
130+
}
131+
}()
132+
if !unique {
133+
panic("crypto/hmac: hash generation function does not produce unique values")
134+
}
135+
blocksize := hm.inner.BlockSize()
136+
hm.ipad = make([]byte, blocksize)
137+
hm.opad = make([]byte, blocksize)
138+
if len(key) > blocksize {
139+
// If key is too big, hash it.
140+
hm.outer.Write(key)
141+
key = hm.outer.Sum(nil)
142+
}
143+
copy(hm.ipad, key)
144+
copy(hm.opad, key)
145+
for i := range hm.ipad {
146+
hm.ipad[i] ^= 0x36
147+
}
148+
for i := range hm.opad {
149+
hm.opad[i] ^= 0x5c
150+
}
151+
hm.inner.Write(hm.ipad)
152+
153+
return hm
154+
}
155+
156+
func setServiceIndicator(h1, h2 fips.Hash, key []byte) {
157+
if len(key) < 112/8 {
158+
return
159+
}
160+
switch h1.(type) {
161+
case *sha256.Digest, *sha512.Digest:
162+
// TODO(fips): SHA-3
163+
default:
164+
return
165+
}
166+
switch h2.(type) {
167+
case *sha256.Digest, *sha512.Digest:
168+
// TODO(fips): SHA-3
169+
default:
170+
return
171+
}
172+
// TODO(fips): set service indicator.
173+
}

src/go/build/deps_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -447,9 +447,11 @@ var depsRules = `
447447
# It must not depend on external crypto packages.
448448
# Internal packages imported by FIPS might need to retain
449449
# backwards compatibility with older versions of the module.
450-
RUNTIME, crypto/internal/impl
450+
STR, crypto/internal/impl
451+
< crypto/internal/fips
451452
< crypto/internal/fips/sha256
452453
< crypto/internal/fips/sha512
454+
< crypto/internal/fips/hmac
453455
< FIPS;
454456
455457
NONE < crypto/internal/boring/sig, crypto/internal/boring/syso;

0 commit comments

Comments
 (0)