HTTP Message Signature Base Builder

Rebuild the RFC 9421 signature base from a raw HTTP message and its Signature-Input header, line by line, with the rule that produced each line. Everything runs in your browser.

RFC 9421 (HTTP Message Signatures) does not sign the bytes that went over the wire. It signs a string you have to reconstruct: the signature base. A signature that fails to verify tells you nothing on its own. The base does. One wrong byte anywhere in it changes every byte of the MAC, which is exactly what the trailing newline control in the self test below demonstrates.

Scope. This page implements RFC 9421 only. The obsolete draft-cavage scheme, the one whose Signature header carries keyId=... and headers="...", is not supported and will not parse here. Paste a Signature-Input value, not a cavage Signature value.

This is an independent tool. It is not affiliated with, endorsed by, or connected to the IETF, the RFC Editor, or any implementation of RFC 9421. Behaviour was reimplemented from the published specification at https://www.rfc-editor.org/rfc/rfc9421.txt and no code is reproduced here.

Signature base

HMAC-SHA-256 check (optional)

Symmetric only, by design. See why.

Why two conforming stacks end up with different bases

A verifier does not choose ;bs or ;sf. Those parameters travel inside the Signature-Input inner list, and a conforming verifier reads them out of that header and applies them. Divergence comes from somewhere else. Three real cases:

1. The verifier cannot serialize the type

Section 2.1.1 says it outright:

If the application does not know the type of the field or does not know how to serialize the type of the field, the use of this flag will produce an error. As a consequence, the signer can only reliably sign fields using this flag when the verifier's system knows the type as well.

That is why this page refuses to guess a Structured Field type. When a component carries ;sf, you declare the type or the build aborts.

2. An intermediary merged or split a multi-valued field

Without ;bs, the two forms below collapse to an identical base line, so a merge is invisible. With ;bs, they do not, which is the whole point of the flag. All four strings are from Section 2.1.3.

Two field instances

Example-Header: value, with, lots
Example-Header: of, commas

no parameter

"example-header": value, with, lots, of, commas

with ;bs

"example-header";bs: :dmFsdWUsIHdpdGgsIGxvdHM=:, :b2YsIGNvbW1hcw==:

One merged instance

Example-Header: value, with, lots, of, commas

no parameter, identical to the left

"example-header": value, with, lots, of, commas

with ;bs, now different

"example-header";bs: :dmFsdWUsIHdpdGgsIGxvdHMsIG9mLCBjb21tYXM=:

All four of those lines are recomputed by the self test below, from the two messages, rather than being printed here as decoration.

3. The signer applied ;sf and an intermediary reserialized the field

With ;sf the base line carries the strict serialization of the parsed structure. If an intermediary rewrites the field on the way, for example by collapsing internal whitespace or reordering nothing but reserializing anyway, signer and verifier parse different input and produce different bases from the same nominal field.

The @query-param encoder

Section 2.2.8 form-decodes the name and the value, then re-encodes with the urlencoded percent-encode set. Two obvious shortcuts are both wrong, in opposite directions. URLSearchParams turns a space into a plus sign, and the RFC's own printed base line requires %20. encodeURIComponent leaves ! ~ ' ( ) unescaped, and the urlencoded set escapes those.

What this page does: form-decode (every plus sign becomes a space, then percent-decode), then re-encode byte by byte, keeping literal only the alphanumerics plus * - . _, and emitting every other byte as a percent sign and two uppercase hex digits. The three worked examples below come straight from the Section 2.2.8 query string and are recomputed live by the self test.

Query parameter as sentBase line this page produces

Structured Fields types this page implements

A partial serializer emits a wrong base with total confidence, which is worse than emitting nothing. So the list is closed and it is short. For ;sf and ;key, this page implements:

Anything outside that list raises a visible error naming the unsupported type. Per Section 2.5 the page also raises, never guesses, on a component parameter it does not understand, on one that does not apply to the component it is attached to, and on ;bs combined with ;sf, which the RFC names as its example of mutually incompatible parameters.

Conditions that abort the build with no base at all

Section 2.5 requires that all errors "MUST fail the algorithm immediately, without outputting a signature base". This page obeys that literally: on any error it renders the diagnosis and suppresses the base pane entirely. It never renders a partial base.

ConditionRFC section
Duplicate component identifier, including its parameters2.5 step 2.1
@signature-params enumerated inside the covered component list2.3
@status used in a request2.2.9
A request-targeted derived component used on a response without ;req2.2
;req used on a signature whose target message is a request2.5 step 2.5
;req used but no originating request supplied2.4
;bs combined with ;sf, or with ;key2.5 step 2.5, 2.1
A component parameter the tool does not understand2.5 step 2.5
A component parameter that does not apply to the component it is attached to: ;sf, ;key, ;bs or ;tr on a derived component, or ;name on anything but @query-param2.5 step 2.5
A named field absent from the message2.5
A ;tr component whose chunked body cannot be walked to its trailer section2.1.4
A ;key naming a Dictionary member that does not exist2.1.2
A @query-param absent from the query, or occurring more than once2.2.8
A ;sf field whose Structured Field type is undeclared or unimplemented2.1.1, 2.5
A component value containing a newline2
Any non-ASCII character in the finished base2.5 step 4

Every error found is reported, not just the first, ordered by position in the covered component list, so fixing a Signature-Input by hand does not cost one round trip per mistake. Separately from the RFC conditions above, a single field value carrying more than 5000 Dictionary members or parameters is refused by name rather than ground through, so a hostile paste cannot freeze the tab.

Why the crypto panel is HMAC-SHA-256 only

No asymmetric verification, no key import, no RSA, ECDSA or Ed25519 path. The reason is in the RFC. Its RSA-PSS and ECDSA examples each carry the note that the algorithm "is non-deterministic, meaning that a different signature value will be created every time the algorithm is run", so comparing a freshly computed value against the printed one is only honest for the deterministic hmac-sha256 case in Appendix B.2.5. The base string is the deliverable here. The MAC is the garnish, and when crypto.subtle is unavailable the page degrades to base-only output and says so.

Self test

The suite runs the RFC's own printed vectors in this page, in your browser, against the same code that builds your base above. It includes a positive control, an assertion written to fail, so a suite that silently stops running is detectable: if the control ever reports PASS, the suite is not doing what it says.

Privacy

Everything on this page runs in your browser. There is no network call of any kind: no request for code, fonts, analytics or telemetry, and nothing you paste is transmitted anywhere. Save the file and it works offline from file://. The one browser API this page reaches for is crypto.subtle, for the optional HMAC, and it is feature detected.