|
| 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 | +} |
0 commit comments