An HMAC tag authenticates exact bytes relative to a shared secret. The standard library provides the construction and comparison operation; application code should not invent either.
The exercise supplies HMAC256 and asks you to verify a complete tag. Published RFC 4231 cases check interoperability. Their keys are intentionally public test fixtures and must not be reused as real secrets.
Use hmac.New with sha256.New, write the exact message and obtain Sum(nil). Repeated writes represent concatenated bytes; they do not create separate authenticated fields by themselves.
Verify the full tag length before hmac.Equal. Equal(nil,nil) is true as a byte comparison, but an empty tag is not a valid record tag under this contract.
package main
import ("fmt";"crypto/hmac";"crypto/sha256")
func main(){
mac:=hmac.New(sha256.New,[]byte("public-test-key"));_,_=mac.Write([]byte("record"));tag:=mac.Sum(nil);fmt.Println(len(tag),hmac.Equal(tag,tag))
}The low-level wrapper follows the primitive’s behavior so published vectors can use their specified key lengths. The final application signer is a stricter boundary: exactly 32 random key bytes, explicit context and bounded framed payloads.
A correct MAC does not hide the message, identify one person among shared-key holders or stop replay. A record protocol must add freshness/sequence state if that is needed. Use separate keys or deliberate domain separation across unrelated purposes.
Test a valid message, changed message, changed tag, wrong key, empty/truncated tag and binary input. A positive round trip alone would not prove the verifier rejects corruption.
package main
import ("fmt";"crypto/hmac";"crypto/sha256")
func main(){
key:=[]byte("test-key");a:=hmac.New(sha256.New,key);_,_=a.Write([]byte("one"));b:=hmac.New(sha256.New,key);_,_=b.Write([]byte("two"));fmt.Println(hmac.Equal(a.Sum(nil),b.Sum(nil)))
}Complete verification and run both published and altered cases. Keep the fixture keys visibly separate from application key generation.
Next we practise authenticated encryption with a nonce-managing standard API rather than building an encryption scheme.
A checksum can match while the record is rejected.
Choose an original or altered fixture. The replay recomputes an ordinary checksum of the shown payload, then runs the actual record verifier with its key, version and expected-context gates. This uses an intentionally public test key, never an application secret.
amount=99Caller expects lab.notesUnkeyed checksum
ConsistentThe advertised digest was computed from these bytes. This needs no secret and does not establish trusted origin.
264c0e0d03dec862da5298c837428b4e1e5f6940f23234a51f7f371f062c06d6Authenticated record
RejectedThe checksum matches the changed bytes, but the original HMAC no longer authenticates them.
None — verification failedInspect the actual record fields
{
"version": 1,
"context": "lab.notes",
"message": "YW1vdW50PTk5",
"tag": "186780a104a57a54b70ce2db7f88b9e310874ecdd184a071e1af14fabde64b7e"
}The record’s Base64 message remains readable. Acceptance means the complete format/context/MAC contract passed; rejection can occur before MAC comparison. This replay does not claim encryption, unique-sender identity or replay protection.
Download the CLI, published vectors and corruption tests →