Skip to content

HMAC signing

Trusted signers (app, CI, worker) and the edge validator share a secret. The browser only sees a public URL with a signature (often s=…) that proves the query was authorized by someone with the key. Exact byte layout—field order, encoding, which query keys are in the MAC—must match your implementation; use the checklist below and your repo’s tests as the source of truth.

The MAC is usually computed over a canonical string built from:

  • Method — Often GET only for public images.
  • Path — Normalized path; be explicit about decoding, trailing slashes, and case.
  • Query — Security-relevant parameters, typically sorted: at least preset (p), expiry (exp), and any allowed overrides. The signature parameter itself (s) is excluded from the input to the MAC.
  • Host (optional) — Some designs bind to a hostname to limit link reuse across domains; it complicates renames and multi-host setups.

Signer and verifier must agree on sorting, encoding, and which parameters are included—subtle drift is the main cause of “signer tests pass but production returns 403.”

Algorithm — HMAC-SHA256 is a common choice. Document whether the key is raw bytes or UTF-8 string-to-bytes. Encode the digest as hex or base64url in the query as you standardize. Use a constant-time comparison of the expected and provided signature strings where your language makes that practical.

  • Entropy — Use a CSPRNG; for HMAC-SHA256, 32 random bytes (256 bits) is a typical key size.
  • Storage — Secrets Manager, SSM SecureString, or CI secrets; not plaintext Terraform in VCS (AWS setup).

Rotation runbook (typical):

  1. Introduce a new key (or key version / kid in the signed payload if you use key ids).
  2. Update signers to use the new key; update the edge to accept both keys during overlap.
  3. Retire the old key after the longest possible signed URL lifetime in the wild has passed.
  1. Parse the request URL (path + query).
  2. Extract the signature parameter and build the canonical string without it (per your spec).
  3. Enforce the exp claim (Expiration) before expensive work, or immediately after full parse—pick one order and test it.
  4. Recompute HMAC with the active secret(s).
  5. Compare to the provided signature; on any failure return 403 and do not call S3 or run the image Lambda.

Fail fast at the edge—that is the point of signing.

  • Separate counters or log lines for missing signature, bad signature, and expired URL when it helps debugging.
  • Do not log full signing keys or entire secret inputs in cleartext in untrusted log aggregators.
  • Correlate spikes in 403s with client releases and deploys.