How to detect if a PDF was edited

What a PDF records about its own history, where to read it, and what each finding does and does not prove.

A PDF is not a picture of a document. It is an append-only container that keeps receipts: when it was written, what wrote it, how many times it was saved, and which objects a later save replaced. Most of that survives editing, because most editors do not bother to clean it up. This page walks the checks by hand, then shows the automated version.

Language matters here. None of these checks prove fraud. They establish facts — "this file was written twice", "two different tools touched the metadata" — and facts have benign explanations as well as malicious ones. Tamperlens reports risk signals, not verdicts, and so should you when you write this up for someone else.

1. Read the document properties

The cheapest check, available in every PDF reader. In Acrobat it is File → Properties; in Preview, Tools → Show Inspector; in a browser viewer, usually Document properties in the toolbar menu. You are looking at four fields from the file's Info dictionary: /Producer, /Creator, /CreationDate and /ModDate.

/Producer is the interesting one. It names the library or application that physically wrote the bytes. A bank statement produced by a core banking system typically carries a server-side library string — an iText, PDFlib, Prawn, wkhtmltopdf, ReportLab or Quartz variant. If instead it says iLovePDF, Smallpdf, Sejda, PDFescape, Adobe Photoshop or Microsoft: Print To PDF, the file passed through a tool whose purpose is to change page content. That is not proof of anything, but it is a fact worth explaining.

The obvious caveat: these fields are strings. Anyone who can edit a PDF can edit them, and some editors strip them entirely. An absent producer is itself mildly interesting — machine generators almost always identify themselves — but it is weak evidence on its own. Metadata is the first check, never the last; see the deeper treatment in PDF metadata forensics.

2. Compare the Info dictionary against XMP

Nearly every modern PDF carries metadata twice: once in the trailer's Info dictionary, and once in an embedded XMP packet — an XML blob, usually stored uncompressed, that you can read with a text editor. A single tool writing a file once fills both stores consistently. Consumer editors very often update one and leave the other stale.

Find the XMP packet

strings statement.pdf | grep -A40 '<x:xmpmeta'

Then compare, field by field: pdf:Producer against Info /Producer, xmp:CreatorTool against /Creator, xmp:CreateDate against /CreationDate, xmp:ModifyDate against /ModDate. A divergence means at least one store was written by a different tool than the other.

The strongest version of this finding is when the two stores disagree about which tool touched the file and only one of them names an editor. The other store did not record it, so the divergence is the only reason that tool is visible at all. Multi-stage publishing pipelines (design app → distiller → post-processor) produce plain divergence legitimately and routinely, so treat a bare mismatch as a question rather than an answer.

3. Interpret CreationDate and ModDate properly

PDF dates look like D:20260104100200+01'00' — year, month, day, hour, minute, second, then a signed UTC offset. Three things are worth checking, and none of them is "is ModDate later than CreationDate", which is the normal case for any saved file.

  • ModDate earlier than CreationDate. Impossible from a correctly running generator. It means timestamps were set by something other than the pipeline that wrote the document, or edited directly.
  • Dates in the future. Clock skew explains minutes, not months.
  • An impossible UTC offset. No real timezone exceeds ±14:00. A +25'00' offset is a hand-typed string.

Also worth noting: a document with a /ModDate but only one revision was re-written whole, not incrementally updated. Both are edits; they just leave different traces. Absence of a ModDate proves nothing — it is trivially removable.

4. Count the revisions

This is the check that does not depend on any string a forger can retype, and it is the reason PDF forensics works at all. PDF supports incremental update: instead of rewriting a file, a tool can append new objects, a new cross-reference section, a new trailer and a new %%EOF to the end. The old bytes stay where they were. A file that has been saved three times can literally contain all three versions.

Count end-of-file markers and cross-reference pointers

$ grep -c '%%EOF' statement.pdf
2
$ grep -abo 'startxref' statement.pdf
118842:startxref
184102:startxref

One %%EOF means the file was written once — either originally, or re-saved whole by a tool that flattened the history. Two or more means bytes were appended after the document was first completed. Each appended trailer carries a /Prev key pointing back at the previous cross-reference section, so the revisions form a chain you can walk backwards to the original.

Benign causes, and there are many: applying a digital signature is an incremental update by design. So is filling an AcroForm, adding an annotation or a comment, or a linearisation pass by some tools. This is why revision count alone is a medium-strength signal — the useful question is what the later revision changed. If a second revision re-writes an object number that already existed in the first and that object holds page, content-stream or image data, the rendered appearance of the document changed after it was generated. That is a much stronger finding than "it was saved twice".

5. Read the trailer /ID pair

The trailer usually contains an /ID array of two byte strings. The specification is explicit about their roles: the first is a permanent identifier assigned when the document was created and must never change; the second is updated by the producing application on every save.

Look at the tail of the file

$ tail -c 400 statement.pdf
trailer
<< /Size 41 /Root 1 0 R /Info 9 0 R
   /ID [<8f2c...a91b> <41de...77c0>] >>
startxref
184102
%%EOF

Two different values are the PDF format's own record that the file was saved again after it was created. It is a clean, spec-defined marker that costs nothing to read. Its weaknesses: some minimal generators omit /ID entirely (which removes the check rather than proving anything), and a tool that rewrites the whole file can set both elements to the same fresh value, erasing the trace.

6. Look for a second subset of the same font

Embedded fonts in PDFs are usually subsetted: only the glyphs the document actually uses are included, and the font name gets a six-uppercase-letter prefix plus a plus sign, like ABCDEF+Helvetica. The prefix is arbitrary; what matters is that a generator writing a document once collects every glyph it needs for a typeface into one subset.

List embedded font names

$ strings statement.pdf | grep -o '/BaseFont *[^ /]*' | sort -u
/BaseFont /ABCDEF+Helvetica
/BaseFont /QWERTY+Helvetica

Two prefixes for one typeface mean glyphs of that typeface were embedded on two separate occasions. That is the trace left when text is added to or replaced in an existing PDF by a different tool than the one that made it — the classic "changed the numbers" signature, and it survives even when the metadata has been scrubbed.

Benign causes: a document assembled by merging PDFs from different sources will legitimately carry multiple subsets of a common typeface, and so will a file where a signature or stamp annotation brought its own font. Read this signal together with the revision history.

7. If it is signed, check what the signature covers

A signature field's /ByteRange is an array of offset/length pairs naming exactly which bytes of the file the signature protects — normally everything except the hole where the signature itself sits. If the last covered byte is not the last byte of the file, the difference was appended after signing and is not protected by that signature.

This is the strongest single structural finding available in a PDF, and it is readable without any cryptography: you compare two numbers. Note the two separate questions. "Does the signature's mathematics validate?" needs the certificate and a trust store. "Do bytes exist outside the signed range?" needs only the file. A viewer can happily report a valid signature while displaying content that the signature never covered — which is why some viewers show "signed, with subsequent changes" and users click past it.

What manual checking misses

The checks above are real, and for a single suspicious file they are often enough. What they cannot do:

  • Correlate. Individually each finding is weak. The signal worth acting on is a consumer editor in the toolchain and a second revision and a full-page raster image. Correlating by hand across thirty statements a day is not a job anyone does well.
  • See inside compressed structures. Cross-reference streams and object streams (PDF 1.5+) are Flate-compressed. grep cannot read them, so a naive %%EOF count is only a floor on the real revision count.
  • Analyse page content. Whether a page is a full-page scan with a handful of visible vector glyphs drawn over it — as opposed to an invisible OCR layer, which is normal — requires walking the content stream and reading text rendering modes.
  • Be consistent. Two analysts reach two conclusions. A deterministic tool gives the same answer for the same bytes, which is what makes an audit trail defensible.

Automating it

Tamperlens does exactly the checks on this page, plus the ones that need a real parser, and returns them as JSON. It parses the raw bytes itself rather than leaning on a high-level PDF library, because libraries normalise away precisely the evidence that matters — load and re-save a file with most of them and the revision history is gone.

Eleven signal families run on every document: incremental updates, metadata mismatch, date anomalies, producer fingerprint, /ID inconsistency, font anomalies, hybrid pages, active content, signature coverage and structural warnings. Each returns a severity, a plain-English detail, and machine-readable evidence (offsets, object numbers, the actual strings). Every one of them is documented — with its benign causes — in the field guide.

Documents are parsed in memory and never written to disk. The engine is CPU-only, calls no third-party service, and is deterministic: the same bytes produce the same report, so you can regression-test against it.

Check a PDF now, free

Drop a file into the checker on the home page — no account, nothing stored, full report. When you want it in your pipeline, an API key with 25 documents a month is free: see the API quickstart or create an account.

Tamperlens reports risk signals, not authenticity verdicts. Signals can have benign causes; combine them with your own decision logic.

Related reading