How Multiple Digital Signatures Work in a PDF

How Multiple Digital Signatures Work in a PDF

A second PDF signature does not modify the first signature. It creates a new revision of the document.

That distinction explains most of the behavior that otherwise looks contradictory. The file has changed, but the first signature can remain cryptographically valid. The second signer sees the first signature, but the first signer never approved anything added afterward. A validator can recover the exact revision each person signed because PDF incremental updates preserve the earlier bytes.

Multiple signatures therefore depend on two mechanisms working together:

  1. Each signature protects a precise range of bytes.

  2. Every later signature is added through an incremental update instead of rewriting the file.

If a PDF library cannot preserve both properties, it cannot safely add a second signature.

A signed PDF is a sequence of revisions

An ordinary PDF ends with a cross-reference section, a trailer, a startxref offset, and an %%EOF marker. An incremental update appends new or changed objects after that marker. It then writes another cross-reference section and trailer. The new trailer points back to the previous cross-reference section through /Prev.

The result is one file containing several complete historical states:

Revision 0
[original objects]
[xref 0]
[trailer 0]
[startxref]
[%%EOF]

Revision 1
[all bytes from revision 0]
[objects added or replaced for signature A]
[xref 1]
[trailer 1 with /Prev -> xref 0]
[startxref]
[%%EOF]

Revision 2
[all bytes from revisions 0 and 1]
[objects added or replaced for signature B]
[xref 2]
[trailer 2 with /Prev -> xref 1]
[startxref]
[%%EOF]

An updated object may reuse an existing object number. The newer cross-reference section points to the newer copy, while the old copy remains in the earlier revision. A reader resolving the current document uses the latest applicable copy. A signature validator can still reconstruct an earlier state from the bytes and cross-reference chain that existed when that state was signed.

This is not an accidental side effect of the format. The PDF 1.7 reference explicitly requires changes after signing to use incremental updates because changing existing bytes invalidates existing signatures.

ByteRange defines what was signed

The cryptographic signature is stored in a signature dictionary. Two entries do most of the structural work:

/ByteRange [0 10500 12502 1498]
/Contents <3082...0000>

/Contents holds the encoded signature value, normally a detached CMS signature. /ByteRange describes the file segments used to calculate the document digest.

In this example, the validator hashes:

  • 10,500 bytes beginning at offset 0

  • 1,498 bytes beginning at offset 12,502

The gap between those segments contains /Contents. It must be excluded because the signature cannot contain itself while it is being calculated.

For a conforming modern signature, the ranges are not an invitation to select arbitrary parts of the document. They cover the signed revision from its %PDF- header through the %%EOF terminating the incremental update that introduced the signature, excluding only the signature value. The PDF 2.0 errata for digital signatures makes this explicit for files containing multiple signatures.

Suppose revision 1 ends at byte 14,000 and contains signature A. A simplified range might be:

Signature A: /ByteRange [0 10500 12502 1498]

Now revision 2 appends 5,000 bytes and ends at byte 19,000. Signature B might use:

Signature B: /ByteRange [0 15500 17502 1498]

Signature A still protects its revision ending at byte 14,000. Signature B protects the entire history through revision 2, including the bytes that contain signature A, while excluding only B's own /Contents value.

The ordering follows from the ranges. A later signature reaches a later %%EOF and therefore covers a longer revision.

What the CMS value actually proves

The PDF structure identifies the bytes. The cryptographic container establishes the signature over their digest.

For interoperable PAdES signatures, /Contents contains a DER-encoded CMS SignedData object and the signature dictionary uses /SubFilter /ETSI.CAdES.detached. The signing process is conceptually:

  1. Hash the two segments named by /ByteRange.

  2. Place that digest in the CMS message-digest signed attribute.

  3. Sign the encoded CMS signed attributes with the signer's private key.

  4. Store the resulting detached CMS object in /Contents.

CMS is standardized in RFC 5652. The PDF-specific profile and validation requirements are defined by ETSI EN 319 142-1.

Verification answers several separate questions:

  • Do the current byte-range hashes match the digest stored in CMS?

  • Does the public key verify the CMS signature?

  • Does the certificate identify the expected signer?

  • Was the certificate trusted and valid at the relevant time?

  • Has it been revoked?

  • Were changes after this signed revision permitted?

A positive answer to the first two questions establishes cryptographic integrity and possession of the corresponding private key. It does not, by itself, establish that the certificate should be trusted or that every later revision is acceptable.

Why the first signature can remain valid after the file changes

The first signature protects revision 1, not every byte that might ever be appended to the file.

When signature B is added, revision 1 remains byte-for-byte identical. Recomputing signature A's digest therefore produces the same result. The appended bytes sit beyond the end of A's signed revision.

This leads to three statements that must not be collapsed into one:

  1. Signature A is cryptographically valid. Its signed bytes have not changed.

  2. The current file contains changes after signature A. Revision 2 did not exist when A signed.

  3. Those changes may or may not be permitted. That depends on certification permissions, field locks, and the validator's modification analysis.

A user interface may summarize these facts as “valid, with changes made after signing.” That is not equivalent to an invalid hash. Nor does A's valid signature mean A approved revision 2.

Signature B provides integrity for revision 2. Because B's byte range includes revision 1, B signed a document containing A's signature. This still does not transfer B's approval backward or A's approval forward. Each signature attests to its own revision.

Approval signatures and certification signatures behave differently

PDF supports ordinary approval signatures and one certification signature. The certification signature uses the DocMDP transform to define which later changes are allowed.

A certification signature must be the first signed field. Its P permission value controls subsequent revisions:

PPermitted changes after certification
1No changes
2Fill forms, instantiate page templates, and sign
3The changes permitted by P = 2, plus annotation creation, deletion, and modification

This choice directly affects a multi-signer workflow. Certifying with P = 1 declares the document final, so a later approval signature conflicts with that policy even if it is appended correctly. P = 2 or P = 3 can permit signing, subject to the document's field structure and field-level restrictions.

The safest workflow creates the required empty signature fields before certification. Later signers fill those fields instead of introducing new form structures after the document has been certified. This also makes the intended order visible and avoids relying on different viewers to classify a newly added field identically.

A signature field can carry a lock dictionary. When that field is signed, FieldMDP can lock all fields, selected fields, or every field except selected ones. This allows a sequence such as:

  1. The author certifies the document while allowing form filling and signing.

  2. A reviewer completes designated fields and signs.

  3. That signature locks the review fields.

  4. A final approver signs a separate field after verifying the prior revision.

The byte-range signatures prove the revisions. DocMDP and FieldMDP define which changes the workflow considers acceptable.

Adding the next signature is an append-only operation

A low-level signer cannot simply load the PDF into an object model and save it again. Many PDF writers produce a logically equivalent file by reordering objects, rebuilding streams, normalizing whitespace, or generating a new cross-reference structure. Any changed byte inside an existing signed range breaks the earlier signature.

A second-signature implementation instead needs to:

  1. Read the latest trailer and cross-reference data.

  2. Preserve every existing byte exactly.

  3. Add or update the new signature field, its widget, the signature dictionary, and any required form references.

  4. Reserve fixed space for /ByteRange values and /Contents.

  5. Append a cross-reference section or stream for the new and replaced objects.

  6. Append a trailer that points to the previous cross-reference section.

  7. Calculate the final offsets and write /ByteRange.

  8. Hash the declared segments.

  9. Produce the detached CMS signature.

  10. Hex-encode it into the reserved /Contents space without changing the revision's length.

The placeholder solves a circular dependency. The byte ranges cannot be finalized until the new revision has been serialized, but the signature value changes the serialized file. Reserving the signature space makes the offsets stable. /ByteRange is patched before hashing; /Contents is patched afterward and is outside the hash.

The CMS result is not perfectly predictable in size. Certificate chains, signed attributes, and timestamp tokens all contribute bytes. If the encoded result exceeds the reservation, the writer must rebuild the incremental update with a larger placeholder and calculate the signature again. Expanding /Contents after signing changes offsets and invalidates the result.

Common ways to break an earlier signature

Rewriting instead of appending

This is the most consequential failure. The pages may render identically, but object compression, metadata normalization, or a changed line ending is enough to alter the digest.

Hashing only the original document

The new signature must cover the complete revision that introduces it, excluding its own value. Hashing only the preceding revision leaves the new signature field, cross-reference data, and trailer outside the signature.

Reusing a signed signature field

Each signer needs a separate signature field and signature dictionary. Replacing the /V value of an already signed field creates a new current object but does not represent two independent signatures correctly.

Miscalculating the placeholder offsets

/ByteRange contains byte offsets, not character positions. Multibyte text, line-ending conversion, a UTF-8 byte-order mark, or writing in text mode can make apparently correct arithmetic wrong.

Ignoring certification permissions

A valid byte-range digest does not override DocMDP. A later signature can be cryptographically sound while violating the first signer's declared modification policy.

Treating the visual appearance as the signature

The widget appearance is page content used to display a name, date, or graphic. Cryptographic verification uses the signature dictionary and CMS value. Copying an appearance does not copy a signature, and changing an appearance can itself become a prohibited document modification.

Assuming certificate validity lasts forever

A certificate can expire, its revocation service can disappear, and an algorithm can become unsuitable. Long-term validation requires more than preserving the original CMS bytes.

Human signatures, timestamps, and long-term validation are separate layers

A document timestamp is also represented through a signature dictionary and an incremental update, but it is not another person's approval. It uses a trusted timestamp token to prove that a particular document revision existed before a particular time.

The PAdES baseline levels add evidence progressively:

  • B-B provides the basic signature.

  • B-T adds trusted time evidence.

  • B-LT includes certificate and revocation material needed for later validation.

  • B-LTA protects that validation material with document timestamps and can add further timestamps over time.

Validation material is stored in the Document Security Store (DSS). It can be appended after signing without changing the earlier signed bytes. Future DSS updates should retain the material from previous revisions rather than replacing it with only the newest data.

An RFC 3161 timestamp does not prove that a human approved the document. It proves that a timestamp authority processed a digest by a given time. The distinction matters when a PDF contains two approval signatures, several document timestamps, and later validation-data updates: all are incremental revisions, but they make different claims.

How to test a multi-signature writer

A viewer showing two green check marks is not enough. The implementation should be tested at the byte, revision, policy, and trust levels.

At minimum, verify these cases:

  1. Sign revision 1 and validate signature A.

  2. Append signature B and validate both A and B.

  3. Confirm that revision 1 can be extracted and rendered independently.

  4. Change one byte inside revision 1 and confirm that both signatures fail their digest checks.

  5. Change a byte introduced only in revision 2 and confirm that B fails while A's historical byte range remains intact.

  6. Rewrite the whole signed file and confirm that A fails even if the pages look unchanged.

  7. Certify with P = 1 and confirm that a later signature is reported as disallowed.

  8. Certify with the intended multi-signer permissions and sign the prepared fields in order.

  9. Test a CMS value larger than the reserved placeholder.

  10. Validate with more than one independent implementation.

Poppler's pdfsig provides a useful independent check:

pdfsig signed-document.pdf

It should supplement, not replace, application-level tests that inspect revision boundaries and modification permissions.

The architecture follows from the file format

Supporting multiple PDF signatures is not mainly a matter of calling the signing operation twice. It requires a writer that treats every signed revision as immutable.

The practical design rules are strict:

  • Preserve all existing bytes.

  • Add each signature through an incremental update.

  • Give every signer a separate field.

  • Make each ByteRange cover the complete revision except its own Contents.

  • Plan certification permissions before the first signature.

  • Distinguish cryptographic validity from allowed modifications and certificate trust.

  • Add timestamps and validation material when the signature must remain verifiable long after certificate expiry.

Once those rules are enforced, the apparent contradiction disappears. The PDF changes because a new revision is appended. The earlier signature remains valid because its revision does not change.

Sources