Logo

Signing API Requests: HMAC, Timestamps and Replay

A key proves who you are. A signature proves what you asked for. The gap between those two is where replay attacks live.

August 15, 20266 min readAIO Research Team
Signing API Requests: HMAC, Timestamps and Replay

An API key answers one question: who is calling. A signature answers a better one: was this exact request authorised, and has anything about it changed since.

The difference matters because a key on its own can be captured and reused with different contents.

What gets signed

Not the whole request in some vague sense. A specific, documented string built from its parts. On AIO that is the algorithm, the date, the method and path with query, and an MD5 of the body, joined in a fixed order and signed with your secret using HMAC-SHA256.

The construction has to match the server byte for byte. This is the whole reason signatures fail.

Why the timestamp is there

Without it, a signature is valid forever. Anyone who captures one valid request can replay it later and the signature still verifies, because nothing in it says when it was meant to happen.

Including the timestamp in the signed string, and having the server reject anything outside a tolerance window, binds the request to a moment. AIO uses a 300 second window, which is why a server with a badly drifted clock fails every call.

If signatures start failing across the board, check the clock before you check the code. Run NTP.

Why the body hash is there

Signing the body's hash rather than the body keeps the signed string short while still binding every byte of the payload. Change one digit of an amount and the hash changes, so the signature no longer matches.

That is what stops an intercepted request being edited and forwarded.

Where implementations go wrong

Canonicalisation

The single biggest cause of a failing signature. Hash the body you are actually sending, in the exact bytes you send. If your HTTP client re-serialises the JSON after you hash it, whitespace and key order shift and the hash is wrong.

Serialise once to a string. Hash that string. Send that string.

Query parameter order

If the path and query are part of the signed string, the order and encoding must match what you actually transmit. Do not rebuild the query separately from the request.

Trailing slashes and path rewrites

A proxy that normalises the path after you have signed it will break every call. If a gateway rewrites, know what the server sees.

Debugging, in order

  1. Log the exact string you signed. Not the pieces, the joined result.
  2. Compare it character by character with the documented format.
  3. Check the clock.
  4. Confirm you hashed the transmitted body and not a reconstructed one.
  5. Confirm the secret is the right one for the environment.

That order finds it almost every time, and usually at step two.

The same idea, inbound

Callbacks coming the other way use the same principle. You verify their signature before acting, exactly as the server verifies yours. Both directions, or neither is secure.

Frequently Asked Questions

Why sign API requests instead of just using an API key?

A key only proves identity. A signature proves the specific request was authorised and has not been altered in transit, so an intercepted request cannot be modified and replayed with a different amount or destination.

What goes into an HMAC signature for an API request?

Typically the method, the path and query, a timestamp, and a hash of the body, combined in a documented order and signed with your secret. The exact string must match the server's construction byte for byte or the signature will not verify.

What is a replay attack?

Capturing a valid signed request and sending it again later. Because the signature is still valid, the server would accept it unless something binds the request to a moment in time, which is what the timestamp and its tolerance window do.

Why does my signature fail to verify?

Almost always a canonicalisation difference: the body hashed after re-serialisation, a trailing slash, differently ordered query parameters, or a clock more than the tolerance out of sync. Log the exact string you signed and compare it with the documented format.

Related News

Continue exploring the latest updates and insights from our blog.