Message authentication with HMAC
Attach a keyed tag to a message so that tampering is detected by anyone who holds the key.
import hashlib
import hmac
key = b"shared-secret-key"
def sign(message):
return hmac.new(key, message, hashlib.sha256).hexdigest()
def verify(message, tag):
return hmac.compare_digest(sign(message), tag)
message = b"amount=100&to=alice"
tag = sign(message)
print(verify(message, tag))
print(verify(b"amount=900&to=alice", tag))
print(verify(message, "0" * 64))
print(len(tag))True False False 64
Explanation
A message authentication code (MAC) is a short tag computed from a message and a secret key. A receiver who has the same key recomputes the tag. If it matches, the message is intact and came from someone who knows the key. Without the key, an attacker cannot produce a valid tag for a changed message.
HMAC builds a MAC from a hash function. It is defined in RFC 2104 and has the form HMAC(K, m) = H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ m)). HMAC is not the same as hashing key + message, because that simple construction is vulnerable to length-extension attacks with hashes such as SHA-256; HMAC is not.
A MAC gives integrity and authenticity between parties that share a key, but not non-repudiation: either party could have created a valid tag, so it cannot prove who sent it. Digital signatures, which use a private and a public key, provide that. The program signs a payment request, verifies it, and shows that a changed message or a guessed tag is rejected. Always compare tags with hmac.compare_digest. Keys must be random, secret, and long enough, for example 32 random bytes; the string used here is only a readable stand-in.
Explore the dry run
import hashlibimport hmac key = b"shared-secret-key" def sign(message): return hmac.new(key, message, hashlib.sha256).hexdigest() def verify(message, tag): return hmac.compare_digest(sign(message), tag) message = b"amount=100&to=alice"tag = sign(message)print(verify(message, tag))print(verify(b"amount=900&to=alice", tag))print(verify(message, "0" * 64))print(len(tag))Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| HMAC-SHA256 mixes the secret key into the hash, producing a 64-hex-character tag. | — | — | |
| The receiver recomputes the tag with the shared key and gets the same value. | — | True | |
| A changed message yields a different tag, so the original tag no longer matches. | — | True False | |
| A guessed tag is rejected because the attacker does not know the key. | — | True False False | |
| SHA-256 produces a 32-byte tag, which is 64 hexadecimal characters. | — | True False False 64 |
Cost and common mistakes
Computing a tag is O(n) in the message length. Verification costs the same.
- Do not build a MAC as hash(key + message). Use HMAC, which is designed to avoid length-extension attacks.
- Compare tags with hmac.compare_digest, not ==, to avoid timing side channels.
- A MAC does not hide the message. It protects integrity and authenticity, not confidentiality.
Try it yourself
What does a MAC prove that a plain SHA-256 hash of the message does not?
Reveal answer and reasoning
That the message was produced by someone who knows the secret key. Anyone can compute a plain hash, so an attacker could replace both message and hash.