What part of a multipart body gets signed
Signing schemes treat a multipart body in one of three ways, and the choice decides how much of the wire format matters.
The first signs the whole encoded body. The digest covers every byte the client sends, from the opening boundary line to the closing boundary marker. The second signs a single field, such as a policy document, a metadata string, or a hash of the file, and leaves the rest of the body unsigned. The third signs nothing in the body and relies on a signed header, a session value, or a token issued before the upload starts.
The second arrangement is common for uploads because hashing a large file twice is expensive. The signature covers a small field that names the file and the conditions of the upload, and the server checks the uploaded bytes against the name and the length it already approved. The first arrangement appears in private APIs where the whole request is treated as one blob. The third appears where the upload endpoint is separate from the signing endpoint and the signature is really an authorization to upload.
Boundary and part order change the signed bytes
A multipart body is delimited by a boundary string the client invents, and that string appears in the body many times. When the scheme signs the whole body, the boundary is signed material whether the provider intended it or not. A library that generates its own boundary produces a body that is semantically identical and cryptographically different, so the signature fails while every visible field looks right.
Part order matters for the same reason. A JSON object has a canonical form that many schemes normalise by sorting keys, but a multipart body has no canonical form at all. The bytes are the order the client wrote them, so a client that reorders parts or re-encodes a parameter changes the digest.
Two more details belong to the same problem. Some schemes accept the upload only when the file part comes first, because the server streams the body and validates the metadata as it writes. Others require the metadata part first so the server can check the signature before it accepts a large file. Both are order rules that live in the verifier, not in the format.
Filename and content type belong to the part headers
Each part begins with its own headers, separated from the body by a blank line. The disposition header usually carries a name and a filename, and the content type header names the media type of that part. When the whole body is signed, those header values are signed too, including the way the filename is quoted and the exact media type string.
- The filename text is signed byte for byte. A file named without an extension, or with a different extension, produces a different digest even when the file itself is unchanged.
- The media type string matters. A generic binary type and a specific image or document type are different bytes, and servers that validate the declared type reject a mismatch.
- Missing headers change the digest. Libraries add a content transfer encoding or a content type with a charset parameter that the original client did not send, and each addition shifts the bytes.
- Escaping rules differ. Quoting and escaping of non-ASCII characters in a filename is not uniform across libraries, which is a common source of mismatched digests on non-English filenames.
Why a re-serialised body changes the signature
A request library accepts a structure of fields and encodes it for you. That convenience is the problem in a signed upload. The library picks a boundary, may reorder parts, may add or drop headers per part, and may normalise line endings, and each of those decisions moves the bytes away from the ones the signature was computed over. The signature was produced over a specific byte sequence, and the library produces a different one from the same fields.
Two practical consequences follow. First, compare bytes rather than fields when an upload fails, because a diff of the encoded bodies localises the mismatch immediately. Second, when a large file is involved, check whether the signature covers the file or a hash of it, since that decides whether the file can be streamed from disk or must be assembled in memory.
Working an upload endpoint
The method that costs least is to take a captured multipart body and resend the exact bytes, boundary included, before changing anything. If that request succeeds, the scheme signs the whole body and the byte sequence is the reference. If it fails while the captured request succeeded from the app, then the signed material is a field, a header, or a session value, and the next tests narrow it further.
From that baseline, change one part header and watch the response, then change the part order, then change a filename. Each change tells you whether that element sits inside the signed material. Where the answer is yes, the client reproduces the original encoding rather than a semantically equivalent one, and the encoded body becomes part of the recovered algorithm.
Some clients need to build the body as bytes directly rather than through a helper. That work is ordinary once the rules are known, and it is the difference between an upload that signs correctly from a script and one that signs correctly only in the app.
Related work
Reviewed 28 September 2026 · SReverse research desk