How to Sign and Verify a Message with HMAC
HMAC is how you prove a message really came from someone holding a shared secret and was not altered in transit — the mechanism behind most webhook signatures and API request signing. It is simple to use and hard to misuse, but a handful of details (constant-time comparison, canonical payloads, replay protection) decide whether it actually protects you. This guide covers each one.
In plain words
Signing a message with HMAC means authenticating it with a secret both sides share, and PlayCISO has a free, browser-based HMAC Lab to see how it works. Use HMAC-SHA-256 or stronger, a high-entropy key, a canonical form of the payload, and verify with a constant-time comparison.
HMAC is immune to length-extension attacks by design, so you never hash the secret and message together yourself. Add a timestamp to defeat replay, and support two valid keys at once so you can rotate without downtime.
Step by step
- 1
Decide whether HMAC is the right tool
HMAC authenticates a message using a secret shared between sender and verifier — both sides hold the same key. That is perfect for webhooks, API request signing and internal service-to-service calls where you control both ends. If you need the verifier to be unable to forge messages, or third parties to verify without holding the secret, you want an asymmetric signature (Ed25519 or RSA) instead.
- 2
Choose a strong hash — SHA-256 or better
Use HMAC-SHA-256 as a sensible default, or SHA-384/SHA-512 for higher margins. Avoid HMAC-MD5 and HMAC-SHA-1; while HMAC is more resilient to collisions than raw hashing, there is no reason to build new systems on deprecated hashes. The output size should comfortably exceed any brute-force or birthday concern for your threat model.
- 3
Generate and protect a high-entropy key
Create the HMAC key from a cryptographically secure random source, at least as long as the hash output (32 bytes for SHA-256). Do not use a human-chosen password or a short shared string. Store the key in a secrets manager, scope it to the one integration it serves, and never embed it in client-side code or logs.
- 4
Canonicalize the payload before signing
Both sides must agree on the exact bytes being signed. Define a canonical form — fixed field order, consistent encoding, no incidental whitespace, explicit handling of which headers or timestamps are included — and sign that. Ambiguity here is a real vulnerability: if the verifier reconstructs the signed bytes differently, either valid messages are rejected or an attacker finds two interpretations of one signature.
- 5
Compute and transmit the tag
Run HMAC over the canonical payload with the shared key to produce the tag, then send it alongside the message, typically as a hex or base64 header. Include a timestamp or nonce in the signed data so a captured message cannot be replayed later, and have the verifier reject tags whose timestamp is outside a short tolerance window.
- 6
Verify with a constant-time comparison
On receipt, recompute the HMAC over the canonicalized payload with the same key and compare it to the supplied tag using a constant-time comparison function (for example hmac.compare_digest or crypto.timingSafeEqual). A normal equality check returns early on the first differing byte and leaks timing that can let an attacker recover a valid tag byte by byte. Reject on any mismatch.
- 7
Rotate keys without downtime
Support more than one valid key at a time so you can rotate without breaking in-flight messages. Introduce a new key, accept signatures from both old and new during an overlap window (optionally a key ID in the message tells the verifier which to use), move signers to the new key, then retire the old one. Rotate on a schedule and immediately if a key may have leaked.
Sign a payload, flip a byte and watch verification fail, and see why constant-time comparison matters — free, in your browser.
Related
Every PlayCISO cryptography how-to in one place.
Validate a JWTHMAC (HS256) is one way a JWT is signed — how to verify it correctly.
Encrypt data with AES-GCMWhen you need confidentiality and integrity, not just authentication.
All free toolsThe full PlayCISO toolbox of scanners and reference tools.
Frequently asked questions
What is the difference between HMAC and a digital signature?
HMAC uses a single secret key that both the signer and the verifier hold, so anyone who can verify can also forge. A digital signature uses a private key to sign and a public key to verify, so the verifier cannot create new signatures and third parties can verify without any secret. Use HMAC when you control both ends and want speed; use signatures when verifiers should not be able to forge.
Why does the comparison need to be constant-time?
A normal string or byte comparison stops at the first position that differs, so it takes slightly longer the more leading bytes match. An attacker who can measure that timing across many attempts can discover a valid tag one byte at a time. A constant-time comparison always examines the full length regardless of where differences occur, removing that signal. Use the library function built for it rather than rolling your own.
Do I need to worry about length-extension attacks with HMAC?
No. Length-extension attacks affect naive constructions like hash(secret || message) using Merkle-Damgard hashes such as SHA-256. HMAC was specifically designed with its nested inner and outer hashing to be immune to length extension. That is one of the main reasons to use HMAC instead of just hashing the secret and message together.
Why does canonicalization matter so much?
HMAC signs exact bytes, so the signer and verifier must produce byte-identical representations of the message. If field order, encoding, or whitespace can differ, a verifier might reconstruct different bytes than were signed, which breaks valid messages or, worse, lets an attacker exploit the ambiguity. Define one canonical form and have both sides follow it precisely.
How do I stop someone from replaying a signed message?
A valid signature does not prove freshness, so include a timestamp or a nonce in the signed payload and reject messages outside a short time window or with a nonce you have already seen. Many webhook schemes sign a timestamp and the body together and reject anything older than a few minutes. Without this, a captured valid request can be replayed verbatim.
How should I rotate an HMAC key?
Allow two keys to be valid at once. Add the new key, accept signatures made with either key during an overlap window, switch signers over to the new key, then remove the old one. Including a key identifier in the message lets the verifier pick the right key immediately. Rotate on a schedule and right away if the key might have been exposed.