What is the Signature header?
The Signature header, defined in Section 4.2 of RFC 9421 (HTTP Message Signatures, Standards Track, February 2024), carries the output of signing an HTTP request or response. It is a Dictionary Structured Field: each member’s key is a label, and its value is a Byte Sequence, which is base64 wrapped in colons.
Signature: sig1=:pxcQw6G3AjtMBQjwo8XzkZf/bws5LelbaMk5rGIGtE8=:On its own the value means nothing. What was signed, with which key and when, is declared under the same label in Signature-Input. RFC 9421 requires every message signature to use both headers with the same labels, and treats a label present in one but not the other as an error.
A message can carry several signatures at once, from one signer using different keys or algorithms, or from different parties. Each needs a unique label, and they can share one header or be split across several.
The algorithms registered by RFC 9421 are:
alg value |
Algorithm |
|---|---|
rsa-pss-sha512 |
RSASSA-PSS using SHA-512 |
rsa-v1_5-sha256 |
RSASSA-PKCS1-v1_5 using SHA-256 |
hmac-sha256 |
HMAC using SHA-256 (shared secret) |
ecdsa-p256-sha256 |
ECDSA, curve P-256, SHA-256 |
ecdsa-p384-sha384 |
ECDSA, curve P-384, SHA-384 |
ed25519 |
EdDSA using curve edwards25519 |
JWS algorithms from the JOSE registry can also be used, identified through the key rather than through alg.
API Usage & Best Practices
- Prefer an asymmetric algorithm, and default to
ed25519. It is fast, has small keys and signatures, and is deterministic, so a test vector produces the same bytes every time. Usehmac-sha256only when both ends are yours: anyone who can verify an HMAC signature can also forge one, so it proves nothing to a third party. See webhook signatures for why that matters once a payload is archived. - Verify the signature, then the digest, then the age. A signature covering
content-digestonly proves the header was not changed. Recompute theContent-Digestover the body you actually received, compare it, and checkcreatedagainst your maximum age. Skipping any of the three makes the other two decorative. - Require the signatures your profile names, and tolerate the rest. If your profile says every request carries a signature from a known key covering a stated set of components, reject a request that has none. Labels are chosen by the signer, so match on key and coverage, not on the label name. Extra signatures from intermediaries are legitimate and should not cause a failure.
- Do not sign over another
Signature. Section 7.3.7 calls covering an existing signature value NOT RECOMMENDED because of known weaknesses. A proxy that validates a client signature should add its own signature over the components it vouches for, as in the example below. - Do not confuse it with the older Cavage format. The pre-RFC
draft-cavage-http-signaturesused aSignatureheader withkeyId=,algorithm=,headers=andsignature=parameters, and that format is still common in ActivityPub software. It is not RFC 9421 and the two are not interoperable. If you see a comma-separated list ofname="value"pairs instead of alabel=:base64:dictionary, it is Cavage. - Return
401with a problem body when verification fails. RFC 9421 defines no status code.401 Unauthorizedsuits a missing or invalid signature that stands in for authentication, and400 Bad Requesta header that does not parse. Say which check failed, but not which byte. - Use it on top of TLS, not instead of it. Signatures give integrity and authenticity, never confidentiality. See request signing and HTTP Message Signatures.
Examples
HMAC-signed request, from the RFC 9421 Appendix B.2.5 test vector. The shared secret is the one published in Appendix B.1.5, so you can check an implementation against it:
POST /foo?param=Value&Pet=dog HTTP/1.1Host: example.comDate: Tue, 20 Apr 2021 02:07:55 GMTContent-Type: application/jsonContent-Length: 18Signature-Input: sig-b25=("date" "@authority" "content-type");created=1618884473;keyid="test-shared-secret"Signature: sig-b25=:pxcQw6G3AjtMBQjwo8XzkZf/bws5LelbaMk5rGIGtE8=:
{"hello": "world"}Two signatures on one message, from RFC 9421 Section 4.3: the client’s original under sig1, and a reverse proxy’s under proxy_sig. The proxy has rewritten Host, so the client’s signature no longer verifies at the origin; the proxy’s signature covers the new authority and the Forwarded header it added, and expires a minute after creation:
POST /foo?param=Value&Pet=dog HTTP/1.1Host: origin.host.internal.exampleDate: Tue, 20 Apr 2021 02:07:56 GMTContent-Type: application/jsonContent-Length: 18Forwarded: for=192.0.2.123;host=example.com;proto=httpsContent-Digest: sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:Signature-Input: sig1=("@method" "@authority" "@path" "content-digest" "content-type" "content-length");created=1618884475;keyid="test-key-ecc-p256", proxy_sig=("@method" "@authority" "@path" "content-digest" "content-type" "content-length" "forwarded");created=1618884480;keyid="test-key-rsa";alg="rsa-v1_5-sha256";expires=1618884540Signature: sig1=:X5spyd6CFnAG5QnDyHfqoSNICd+BUP4LYMz2Q0JXlb//4Ijpzp+kve2w4NIyqeAuM7jTDX+sNalzA8ESSaHD3A==:, proxy_sig=:S6ZzPXSdAMOPjN/6KXfXWNO/f7V6cHm7BXYUh3YD/fRad4BCaRZxP+JH+8XY1I6+8Cy+CM5g92iHgxtRPz+MjniOaYmdkDcnL9cCpXJleXsOckpURl49GwiyUpZ10KHgOEe11sx3G2gxI8S0jnxQB+Pu68U9vVcasqOWAEObtNKKZd8tSFu7LB5YAv0RAGhB8tmpv7sFnIm9y+7X5kXQfi8NMaZaA8i2ZHwpBdg7a6CMfwnnrtflzvZdXAsD3LH2TwevU+/PBPv0B6NMNk93wUs/vfJvye+YuI87HU38lZHowtznbLVdp770I6VHR6WfgS9ddzirrswsE1w5o0LV/g==:
{"hello": "world"}