Skip to main content

Protecting Requests with HMAC (Including Timestamps & Nonces)

Most APIs use API keys or OAuth tokens, but those alone don’t prevent certain types of attacks—especially replay attacks where a captured valid request is resent later. This article covers how to protect your requests using HMAC signing, timestamps, and nonces.


What is HMAC?

HMAC (Hash-based Message Authentication Code) is a way to prove:

  1. Integrity: The message hasn’t been modified.
  2. Authenticity: The sender knows a secret key.

How it works (high level):

  • Both client and server share a secret key.

  • The client creates a signature:

    signature = HMAC(secret, message)
  • The server recomputes and compares it:

    expected_signature = HMAC(secret, message)
  • If they match → the request is valid.

Learn HMAC basics: Watch this short video


Replay Attacks: The Missing Piece

Even with HMAC, attackers can capture a valid signed request and send it again (e.g., reusing a payment request).


Solution: Add Timestamps and Nonces

1. Timestamp

Include a header:

X-Timestamp: 2025-07-31T20:00:00Z

Server checks:

  • Is it within a window? (e.g., ±5 minutes)
  • If too old or too far in the future → reject.

Effect: stops long-term replay (hours or days later).


2. Nonce

A nonce is a one-time random string:

X-Nonce: 123e4567-e89b-12d3-a456-426614174000

Server keeps a short-lived cache (e.g., Redis) of (api_key, nonce):

  • If it’s reused → reject.

Effect: stops short-term replay (identical requests within that window).


*The Process**

Client signs a canonical string:

POST
/api/v1/payment
SHA256(body)
2025-07-31T20:00:00Z
123e4567-e89b-12d3-a456-426614174000

Generates a signature:

signature = HMAC_SHA256(secret, canonical_string)

Adds headers:

X-Timestamp, X-Nonce, X-Signature

Send Request.

Server checks:

  1. Timestamp is recent.
  2. Nonce not used before.
  3. Signature matches.

Why Both Timestamp and Nonce?

  • Timestamp alone: stops old replays, but not fast duplicates.

  • Nonce alone: prevents duplicates but requires unbounded storage -- Moore's law isn't that good

  • Together:

    • Timestamp bounds nonce storage (only needed for the time window)
    • Nonce ensures uniqueness inside that window.

Key Takeaway

HMAC ensures the request is authentic. Timestamps & nonces ensure it’s also fresh and unique.

Replay protection (HMAC + timestamp + nonce) proves the request is fresh and authentic. If you also want to suppress accidental duplicates inside a short window, add a hash + TTL gate: compute a stable hash of the canonical request and SETNX it in Redis with a TTL. If it already exists, drop it. Different goal, complementary tool.