Rodney Dela Cruz

What a QR code on a document can and can't prove

Rodney Dela Cruz

Technical Co-founder, eSerbisyo

  • document-automation
  • verification

A QR code on an official document proves that a matching record exists in the system that issued it. It does not prove the document in your hand is genuine, and it never will. Any product that tells you otherwise is selling you the feeling of verification rather than the thing.

That distinction is not a defect in the QR code. It is where the security actually lives, and understanding it is what lets you build the thing properly.

I co-founded eSerbisyo, which issues these documents for barangay halls, so most of what follows is an argument about a design we made rather than a survey of QR codes.

What the code actually points at

The code contains a unique request ID and nothing else. It resolves to a page on the issuing system, which looks the ID up in its database and reports what it finds.

This is the whole trick, and it is worth stating plainly because the intuition is usually backwards: the security comes from the database record, not from the code. The QR is an address. The record is the evidence.

A scannable square and a signed one are different products. An unsigned code is a claim about a string of characters, and it acquires value only when a reader carries that string to the party that issued it and is told what the record says. Whatever the code appears to carry on its own is decoration.

What it catches

Verification against a live record catches the failure modes that actually happen in practice:

  • Copies. A photocopy of a genuine document still resolves to the same valid record — so this does not catch copying, it catches nothing there. What it catches is a copy of a document whose record has since been revoked.
  • Revoked and superseded records. This is the real win. When a clearance is cancelled, superseded, or issued in error, the record changes state and every outstanding printout of it becomes detectable. Without a record behind the code, the only thing you can do is look very carefully at the paper.
  • Fabricated documents with no record. A document invented from scratch produces a code that resolves to nothing, which is immediately visible.

The second is the only one on that list a paper process cannot have: paper cannot be withdrawn.

What it cannot catch

Someone can produce a document with a fabricated QR code that has never existed in the system. If the code is not a real request ID, there is nothing to look up, and a verification page that only checks “does this ID exist” will not help you — the ID does not exist, and that is exactly the problem.

The honest framing: a QR code makes forgery hard to pass off and easy to detect. It does not make forgery impossible. Anyone claiming the second thing is selling something.

The distinction that matters in practice is between defeating tampering and defeating fabrication. The first is largely solved by having a record to check against. The second requires verifying the document against something the forger does not control — a digital signature over the content, a physical feature, or a human who has a reason to check.

A genuine code is not an identified person

This is the gap that gets skipped, and skipping it is how a verification tool starts producing confident nonsense.

A valid code tells you a record exists and describes it. It says nothing about who is holding the paper — and the person presenting a genuine clearance is usually not who it was issued to, because clearances get endorsed and handed to employers in normal use.

So the page can say that document 1234 was issued to a named person on a named date. The reader still has to decide whether the person in front of them is that person — a question the system was never asked.

Identity, if your flow needs it, is a second and separate check, and the interface has to say so. A green result means “this document corresponds to a real record” — not “you are looking at the right person.”

Why a screenshot is a replay, not a forgery

Because the code resolves to a live record, it inherits a property photographs and PDFs do not: it can be presented any number of times, by anyone, and look perfect every time.

A screenshot of the verification page, or a forwarded photo of the printed code, satisfies the reader’s check completely. Nothing about the record changed, so nothing about the check changes — a correct answer to the wrong moment.

This is not fixable inside the QR design. What helps is narrowing what the check is used for:

  • One-time or time-boxed consumption. If the receiving party marks the code as used, a second presentation is visibly a second one. A record with state is not a record without it.
  • Binding the check to a transaction. A code consumed against one purpose — one hiring decision, one permit application — limits the window in which a screenshot is worth anything.
  • Accepting that some uses are check-only. For a document someone intends to keep, no consumption model helps.

Revocation is the same idea pointed the other way: it handles a document that should no longer be valid, and does nothing about one that is entirely valid and shown to the wrong person.

Comparing what a code looks like is the wrong test

The most common verification failure is not a clever forgery. It is an inspection.

Somebody holds a phone up, squints at the square, and looks for the mark in the middle. That tests whether the image resembles the thing they remember — a question about their memory, not about the document.

The category error is treating a printed square as a cryptographic object. It is not one. It encodes an identifier, visibly, so anyone can produce one that looks entirely legitimate, and inspection cannot separate the two because nothing in the picture separates them. The code has no more integrity than the paper it is printed on.

Visual-only checks also fail asymmetrically: quietly first, because the person doing them believes they checked, and loudly afterwards, when the forged document is the one that passed.

The fix is unglamorous: the check has to involve the issuing system. If the answer can be arrived at by looking, it was not a verification.

What a verification flow should actually do

  1. Read the code and resolve the request ID.
  2. Look up the record and report its current state: verified, revoked, or not found.
  3. Show what was issued — document type, issue date, issuing office — so a reader is comparing the record against the paper in front of them rather than taking a green tick on faith.
  4. Say what the check cannot establish, rather than implying full authentication.

That fourth step is the one most implementations skip, and skipping it is what turns a verification tool into a rubber stamp.

Where the code comes from, and why that is boring on purpose

The code is generated from the request ID at the moment of issuance. The PDF is produced server-side from a template, and the code is embedded in that same pass so the printed document and the record cannot drift apart.

Two details matter more than they look:

  • A text fallback. If the code will not scan — bad lighting, a worn print, a broken scanner — the request ID is printed as text too. A verification feature that fails on a damaged document is a verification feature that fails exactly when it is most useful.
  • A stable ID with a changing state. The ID identifies the request forever; the record behind it changes. That separation is what makes revocation possible at all.

Generating the code inside the pass that writes the document is why those two stay in step. Generate it separately and you have two artefacts that agree until somebody regenerates one.

What this adds up to

A QR code on a document is a lookup key. Used well, it turns “is this real?” from a judgement call into a two-second check, and it gives you revocation for free — the single most useful property of the whole approach.

Used badly, it is a decorative square that prints a green tick and teaches everyone involved to trust a picture rather than a record.