Authenticate Messages with Python hmac
Sign a byte message with HMAC-SHA256 and verify it using a shared secret key.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: Authenticate Messages with Python hmac
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
Two different keys sit beside the same payment message. The first program hashes the message twice. Predict whether the two so-called signatures differ, and inspect which arguments actually enter hashlib. The broken terminal prints True: both digests are identical. Key alpha and key beta are defined, but neither appears in the hash call. A sender without either key can compute the same digest after changing the payment text. The defect is visible in the source, even though the output looks like a respectable cryptographic value. Read the hash call rather than the variable names above it. The expression sha256(message) receives the same byte string in both branches, so
- 00:42
of course its two digests compare equal. Replacing key alpha with key beta changes a variable that the expression never reads. A plain digest can help detect an accidental edit when the digest itself is trusted, but it cannot tell a receiver which party supplied the data. Anyone able to edit the message can compute a fresh ordinary digest of the edited bytes. The diagnosis prints key alpha, the byte message pay equals five, and a prefix of its ordinary digest. These are separate lines, not proof that the key influenced the digest. Inspect the call to sha256: its only input is message.
- 01:25
The intended test is whether changing just the key changes the tag for the same message. The printed key and hash prefix are placed beside each other, but adjacency in a terminal is not a cryptographic relationship. The message is precisely the bytes pay equals five, so both sides must agree on those bytes before verifying a tag. HMAC mixes a shared secret key into the hash construction; its new call accepts key, message, and SHA-256. A useful test holds the message fixed while switching keys. If the tag never changes, the implementation still ignores its supposed secret.
- 02:06
A SHA-256 digest has thirty-two bytes, conventionally written as sixty-four hexadecimal characters. The digest length does not answer our trust question: who could produce it? With an ordinary hash, everyone who knows the message. HMAC is a specified keyed construction; do not substitute an improvised sha256 of key concatenated with message and assume it has the same properties. The corrected function passes key and message into hmac.new with SHA-256. In this controlled example the terminal shows the prefix e9c92c69ac01.
- 02:48
The website task must return the full lower-case hexadecimal tag, then check against Python's own HMAC implementation. A different key must produce another tag for the same pay equals five message. HMAC offers two forms of the same tag. digest returns thirty-two raw bytes, while hexdigest renders those bytes as sixty- four ASCII hexadecimal characters. Both sides must compare the same representation; sending hex text and treating it as raw tag bytes would fail despite matching key and message. Sign the exact serialized bytes the receiver will reconstruct.
- 03:28
For instance, pay equals five and pay equals zero-five differ as byte strings even if a human interprets them as the same amount. Specify serialization before signing structured data. The last run compares an original tag with one recomputed for pay equals five hundred. It prints False, then compares the original with the same original message and prints True. This checks both mismatch and match behavior. HMAC authenticates exact bytes; it neither hides the message nor stops someone replaying a previously valid message. A valid tag establishes that someone with the shared key authenticated those bytes;
- 04:09
it does not establish that a payment is authorized or affordable. A service must still check the actor, account and amount. It must also prevent the original pay equals five request from being accepted twice, for example by recording a request identifier or nonce. Use compare_digest on full expected and received tags so comparison behavior does not reveal how far a guess matched. Keep the shared key out of logs and rotate it deliberately. Imagine the sender authenticates a structured payment object. If the sender serializes it with one field order while the receiver uses another, both may mean the same payment but compute different tags.
- 04:52
Authentication is over bytes, not human intent. Define the byte serialization, key identity, and expiry before sending a request; then compare complete tags after reconstruction.
Understand the concept
A plain SHA-256 digest tells you whether two byte strings have the same digest, but anyone can compute it for a changed message. It does not establish who created the digest. HMAC combines a secret key with a hash algorithm to authenticate a message between parties that share the key. This lesson uses a fixed public demo key only for reproducible classroom output; a real key must be generated and protected separately.
The broken demonstration accepts a demo key but computes only a plain hash of the message. The key has no effect on the digest. Replacing the message with another one and recomputing the plain hash would be just as easy for a sender without the key. The missing ingredient is not a longer printed digest; it is keyed computation. Always check what bytes actually enter the function.
Call hmac.new(key_bytes, message_bytes, hashlib.sha256).digest() to produce a binary tag. A hexadecimal rendering is convenient in logs and examples, but a real application should avoid logging a usable authentication token alongside confidential data. Compare the received tag with an expected tag using hmac.compare_digest. It reduces timing leakage associated with naive byte-by-byte comparisons, although it does not solve replay or key management.
Verification needs the exact same byte message and key. Changing one byte should make the expected tag different; the lesson tests that condition without claiming a cryptographic proof from one sample. Agree on serialization before signing: different whitespace or encodings produce different bytes. HMAC provides message authenticity to parties sharing the key, not encryption, and a repeated valid message still needs a protocol-level replay defense when that matters.
- A plain hash does not authenticate a sender
- HMAC uses a secret key and message bytes
- compare_digest is used for tag comparison
- HMAC does not encrypt or prevent replay
See it step by step
Read the code, predict the output, then compare it with the result.
01. Plain hashes ignore the key
import hashlib
message = b'pay=5'
print(hashlib.sha256(message).digest() == hashlib.sha256(message).digest())
TrueThe two calculations agree because no key participates; this equality says nothing about who sent the bytes.
02. Sign with a key
import hmac
import hashlib
print(hmac.new(b'class-key', b'pay=5', hashlib.sha256).hexdigest()[:12])
e9c92c69ac01The first twelve hex characters provide a reproducible illustration, not a recommended truncated production tag.
03. Reject a changed message
import hmac
import hashlib
key = b'class-key'
tag = hmac.new(key, b'pay=5', hashlib.sha256).digest()
changed = hmac.new(key, b'pay=500', hashlib.sha256).digest()
print(hmac.compare_digest(tag, changed))
FalseThe tag for the changed byte message differs, so comparison returns False.
A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
A digest is not a sender identity
A plain SHA-256 digest tells you whether two byte strings have the same digest, but anyone can compute it for a changed message. It does not establish who created the digest. HMAC combines a secret key with a hash algorithm to authenticate a message between parties that share the key. This lesson uses a fixed public demo key only for reproducible classroom output; a real key must be generated and protected separately.
The first output is True even if the surrounding application labels one run with a different key. A plain digest is deterministic and public. Recomputing it proves consistency of bytes, not possession of a secret. Treat file checksums and message authentication as separate use cases with different requirements. The code is intentionally small so there is no hidden key operation to infer.
import hashlib
message = b'pay=5'
print(hashlib.sha256(message).digest() == hashlib.sha256(message).digest())
True- Identify the bytes hashed by the plain digest.
- Ask whether a secret influenced the result.
Put the key inside the computation
The broken demonstration accepts a demo key but computes only a plain hash of the message. The key has no effect on the digest. Replacing the message with another one and recomputing the plain hash would be just as easy for a sender without the key. The missing ingredient is not a longer printed digest; it is keyed computation. Always check what bytes actually enter the function.
HMAC accepts byte keys and byte messages. The twelve-character display is for an easily scanned classroom output; a complete tag is returned in the practice task. Do not turn this demonstration into a rule to truncate every real tag. The key here is visibly written in source, so it is not confidential. A real deployment must store and rotate keys deliberately.
import hmac
import hashlib
print(hmac.new(b'class-key', b'pay=5', hashlib.sha256).hexdigest()[:12])
e9c92c69ac01- Compute an HMAC over the exact message bytes.
- Keep the full tag in the task function.
Verify the exact message bytes
Call hmac.new(key_bytes, message_bytes, hashlib.sha256).digest() to produce a binary tag. A hexadecimal rendering is convenient in logs and examples, but a real application should avoid logging a usable authentication token alongside confidential data. Compare the received tag with an expected tag using hmac.compare_digest. It reduces timing leakage associated with naive byte-by-byte comparisons, although it does not solve replay or key management.
The last example changes the payment text and recomputes the tag with the same key. compare_digest returns False. This validates that the exact byte message is part of the authenticated data. Define serialization before signing structured data, because seemingly harmless whitespace changes the byte sequence. Also add timestamps or nonces when a protocol must reject replayed valid messages.
import hmac
import hashlib
key = b'class-key'
tag = hmac.new(key, b'pay=5', hashlib.sha256).digest()
changed = hmac.new(key, b'pay=500', hashlib.sha256).digest()
print(hmac.compare_digest(tag, changed))
False- Recompute a tag for the received bytes.
- Use compare_digest and consider replay separately.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Import hmac and return hmac.new(key, message, hashlib.sha256).hexdigest(). Keep both inputs as bytes.
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Hashing without the key
hashlib.sha256(message).hexdigest()hmac.new(key, message, hashlib.sha256).hexdigest()What happens: A different sender can recalculate the same kind of tag.
The key must participate in the tag computation; printing more hash characters does not add authentication.
Comparing tags with ==
provided_tag == expected_taghmac.compare_digest(provided_tag, expected_tag)What happens: The comparison may leak timing information about matching prefixes.
Use the dedicated comparison for secret tags, then separately design replay protection and key handling.