deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

PDF compression tools break or strip digital signatures, five-tool test shows

Two dev.to experiments show that common PDF compression tools invalidate or delete digital signatures, while pdfsig can still report an edited signed PDF as valid because it only hashes the signed bytes.

PDF compression tools break or strip digital signatures, five-tool test shows

Two companion experiments published on dev.to on October 18 found that feeding a digitally signed PDF to any of five common compression or rewrite tools either invalidates the signature or removes it without a trace. The tests also exposed a verification gap: poppler's pdfsig can report a signed PDF as valid after its page content has been modified, because it only checks the hash over the originally signed bytes.

Signatures protect bytes, not the rendered page

According to the posts (by pm_cheng and iterandum), the author built a one-page fictional agreement containing an 1800×1200 synthetic photo, signed it with pyHanko using a self-made test certificate authority, and checked the 931,980-byte result with poppler's pdfsig 26.08.0 and pyHanko 0.33.0. The signature's ByteRange was [0 923749 931275 705]: a validator hashes the first 923,749 bytes plus a 705-byte tail, skipping the gap where the hex-encoded signature value sits, and the second range ends exactly at the end of the file.

Because the signature is a hash over raw bytes, reordering objects or recompressing a single stream breaks it, even if every character on the rendered page looks identical.

Five tools, five broken or vanished signatures

Every tool tested rewrote the whole file. None of the outputs began with the original bytes, and each went from two %%EOF markers to one.

Tool Output size Signature field
qpdf (plain rewrite) 930,748 B kept, ByteRange copied unchanged
qpdf (object streams) 930,178 B kept
Ghostscript /ebook 39,876 B removed entirely
ImgIng (ebook preset) 64,903 B kept, range still ends at 931,980
iLovePDF (recommended) 37,513 B removed entirely
PDF24 (default) 50,877 B kept, ByteRange copied

pdfsig rejected every output that kept its signature dictionary with the error "Illegal values in ByteRange array", and pyHanko marked them all INVALID. Ghostscript and iLovePDF deleted the signature field entirely, leaving nothing to validate. The author argues that silent removal is the worse outcome: a reader can be warned about a broken signature, but once the field is gone the file looks exactly like one that was never signed.

pdfsig still said valid after edits

The legitimate way to alter a signed PDF is an incremental update, which appends new objects, a cross-reference table and a final %%EOF while leaving the original bytes untouched. The author appended three such revisions: a second signature, a text annotation written with pypdf, and an extra line of page content added with pyHanko. pdfsig reported "Signature is Valid" for all three, adding only a "Not total document signed" note for the content change.

pyHanko went further, diffing the post-signature revisions against the PDF's modification rules (DocMDP and FieldMDP). It returned an acceptable-modifications verdict for the second signature but ILLEGAL_MODIFICATIONS for the content edit. The annotation was also flagged, though the author cautions this may reflect pyHanko's default policy not recognising how pypdf appends annotations ("unexplained xrefs") rather than what Acrobat would show; Acrobat was not tested.

The core lesson: a matching hash proves the signed bytes are intact. It says nothing about what was appended afterwards.

A cheap check: ByteRange end versus file size

The companion post describes a detector built with pypdf 6.20.1 that compares each signature's final ByteRange offset with the file size. If they match, the signature covers everything. If the range ends early, revisions were appended after signing. If it ends beyond the file, the file was rewritten and the signature dictionary was carried over with stale numbers.

In testing, the three incremental updates all came back as "appended", the kept-field rewrites from qpdf, ImgIng and PDF24 all came back as "past EOF", and the Ghostscript and iLovePDF outputs returned nothing because their signature fields were gone. The author notes the limits: a rewrite that grows a file would land in the "appended" branch, and counting %%EOF markers (three in the incremental files, one in every rewrite) might separate the two cases.

The check now drives an upload service: files whose signature covers the whole file are stored as-is and never compressed, appended files are stored and flagged for human review, files with ranges past end-of-file are returned to the uploader, and only unsigned files get compressed. Compression cannot take the incremental route anyway, since an update must preserve every original byte, so a file can only grow.

Why it matters

E-contract pipelines that sign first and compress later are quietly destroying their own evidence. The experiments document three distinct failure modes: signatures that break loudly, signatures that vanish silently, and signatures that look valid while covering only part of the document. Standard verification tools do not distinguish these cases on their own, and a "valid" verdict from a byte-hash checker is not proof that nothing changed after signing. The fixes are inexpensive: compress before signing rather than after, keep signed files out of compressors entirely, and pair hash checks with a revision-aware validator or at least the ByteRange-versus-file-size comparison before trusting the result.

  • #pdf
  • #digital-signatures
  • #compression
  • #pdfsig
  • #pyhanko