How do you compute an HMAC-SHA256 tag in Go, and what must hmac.New's first argument be?
answer
- a keyed hash, not a plain digest
- the first argument is not a hash
- no parentheses after sha256.New
- the result is an ordinary hash.Hash
- key length is not constrained
basics
~20 sCall hmac.New(sha256.New, key) - the first argument is a function that returns a fresh hash.Hash, written without parentheses. The result is an ordinary hash.Hash: write the message into it and call Sum(nil) for the 32-byte tag.
solid answer
~40 s`hmac.New` has the signature `func New(h func() hash.Hash, key []byte) hash.Hash`. The first argument is a **constructor**, so you pass `sha256.New`, not `sha256.New()` - HMAC needs to build two independent instances of the underlying hash internally, and it needs to rebuild them on `Reset`. The second argument is the shared secret; any length is accepted, because HMAC hashes a key longer than the block size down and zero-pads a shorter one. What comes back is a plain `hash.Hash`, so the rest is identical to unkeyed hashing: `mac.Write(body)` (or `io.Copy(mac, r)`), then `mac.Sum(nil)` for a 32-byte tag. Calling `Reset()` returns it to the initial keyed state for the next message - the key is retained.
code
go · 5 linesfunc tag(key, payload []byte) []byte {
mac := hmac.New(sha256.New, key) // sha256.New, not sha256.New()
mac.Write(payload)
return mac.Sum(nil) // 32 bytes for SHA-256
}go deeper
Memorise the three lines: mac := hmac.New(sha256.New, key), mac.Write(payload), tag := mac.Sum(nil). Pass sha256.New with no parentheses - it is the function itself, not a call.
Explain why the parameter is a constructor: HMAC runs the underlying hash twice over key-derived pads, so it must build fresh instances, including on Reset. Also know that any key length is accepted and the tag is always 32 bytes.
Show you know what the tag covers - exactly the bytes written, in order - so the canonical byte form of a payload must be pinned down before anyone computes a tag, or two services will never agree.
Own the key's lifecycle rather than the call: where a signing key lives, how it rotates without downtime, and whether each consumer gets its own key are the decisions this three-line snippet hides from view.
## A keyed hash, exposed as the same interface An ordinary SHA-256 digest is a function of the message alone: anyone who has the message can compute it. An HMAC tag is a function of the message **and a secret key**, so only holders of the key can produce it. Go puts that in `crypto/hmac`, and deliberately gives it the same shape as every other hash. ```go func New(h func() hash.Hash, key []byte) hash.Hash ``` ### The first argument is a function The single most common compile error here is writing `hmac.New(sha256.New(), key)`. The parameter type is `func() hash.Hash` - a **constructor**, not an instance. You write `sha256.New`, the function value itself. Why a constructor? HMAC is defined roughly as `H(outerKeyPad || H(innerKeyPad || message))`. That requires two separate runs of the underlying hash, each starting from a clean state, and it requires rebuilding the inner state whenever `Reset` is called. Handing over a factory lets `hmac` create as many fresh instances as it needs. It also removes an entire failure mode: you cannot accidentally pass in a hash that already has bytes written to it. The same factory shape means any `hash.Hash` implementation can be plugged in - `sha256.New`, `sha512.New`, `sha512.New384` - without `crypto/hmac` knowing anything about them. ### The key `key []byte` has **no length constraint**, which surprises people who expect "must be 32 bytes". HMAC normalises the key to the underlying hash's block size (64 bytes for SHA-256): a longer key is hashed first and the digest used instead, a shorter one is zero-padded up. So a 16-byte key and a 100-byte key are both legal. That said, the key should be uniformly random bytes from a cryptographic source, not a memorable string, and it should be scoped to one purpose. Deriving per-purpose keys from one master secret is its own topic; the point at the API level is that `hmac.New` will not tell you your key was weak. ### The result is just a hash.Hash ```go mac := hmac.New(sha256.New, key) mac.Write(body) tag := mac.Sum(nil) // 32 bytes ``` Because the return type is `hash.Hash`, everything you know already applies: - It embeds `io.Writer`, so `io.Copy(mac, r)` tags a stream, and `fmt.Fprintf(mac, ...)` works. - `Sum(b)` appends the tag to `b` and does **not** change the state, exactly as for an unkeyed hash. - `Size()` is the underlying hash's output size - 32 for SHA-256 - so the tag is 32 bytes, regardless of how long the key was. - `Reset()` returns the value to its initial **keyed** state, ready for a new message with the same key. It does not forget the key, and it does not need to be re-created per message. - It is not safe for concurrent use. One `mac` per request, or one per goroutine. ### What the tag actually covers Exactly the bytes written, in exactly that order - nothing more. If a payload can be serialised two ways (different JSON key ordering, optional whitespace, a header included or not), then the tag is over one specific byte sequence and the other side must reproduce that same sequence. Fixing the canonical byte form is a design decision you make before you write the first `Write` call, and getting it wrong shows up as tags that never line up between two services. ### Why not just hash the key and the message together `sha256.Sum256(append(key, body...))` looks equivalent and is not. SHA-256 is a Merkle-Damgard construction, which means someone who knows a digest and the length of its input can continue hashing from that state and produce a valid digest for a longer message - without ever seeing the key. Concatenation also blurs the boundary between key and message, so different key/message splits can produce the same input bytes. HMAC's two-pass keyed construction exists to close both holes, and it is three characters more typing. ### The shape to memorise Constructor, key, write, sum: ```go mac := hmac.New(sha256.New, key) mac.Write(payload) tag := mac.Sum(nil) ``` Everything else - streaming, prefixed output, reuse - follows from the fact that `mac` is nothing more exotic than a `hash.Hash` that happens to know a secret. Note that the constructor's type must be literally `func() hash.Hash`. A constructor that returns a concrete type - `crypto/sha3`'s `New256() *sha3.SHA3`, for instance - is not assignable to it, because Go function types are invariant; wrap it instead: `hmac.New(func() hash.Hash { return sha3.New256() }, key)`.
- Why does hmac.New take func() hash.Hash instead of an already-constructed hash.Hash?Because HMAC runs the underlying hash twice - an inner pass over the key-derived pad plus the message, and an outer pass over the result - so it needs two independent instances, and it needs fresh ones again on `Reset`. A factory supplies them. It also guarantees `crypto/hmac` starts from a clean hash rather than one a caller has already written into.
- Does the key have to be a particular length, and what is the tag length?The key can be any length: HMAC hashes a key longer than the block size (64 bytes for SHA-256) down to a digest, and zero-pads a shorter one up. The tag length is the underlying hash's output size, so HMAC-SHA256 always yields 32 bytes no matter what the key looked like.
- What does Reset do on the value returned by hmac.New?It returns the value to its initial keyed state, ready to tag a new message with the same key - it does not clear or forget the key. That makes it cheap to reuse one `mac` for many messages in a single goroutine, though sharing one across goroutines is unsafe, as with any `hash.Hash`.
- Why isn't sha256.Sum256 over the key followed by the message an acceptable substitute?That is an unkeyed digest of concatenated bytes. SHA-256's Merkle-Damgard structure lets someone who knows the digest and the input length extend the message and compute a valid digest without the key, and concatenation leaves the key/message boundary ambiguous. HMAC's two-pass keyed construction is specified to avoid exactly these.
A plain digest is a photo of a document - anyone can take one. An HMAC tag is a wax seal: it is made over the same document, but only whoever holds the stamp can produce it.
saying these in an interview costs you the question
- Writes hmac.New(sha256.New(), key) and cannot explain the compile error
- Believes HMAC is just SHA-256 over key plus message concatenated
- Insists the key must be exactly 32 bytes
- Expects hmac.New to return a special type rather than a hash.Hash
- Thinks Reset clears the key
- Assumes the tag length grows with the key length