Can a QR code be copied? Yes. Point a phone at it and you’ve copied it.
A QR code is just data drawn as squares. There’s nothing in it that a camera can’t see, so there’s nothing in it that a camera can’t take. Screenshot, photocopy, forward it on WhatsApp. It’s all the same code afterwards.
So “a QR code that can’t be copied” isn’t the real goal. The real one is a code where the copy is worth nothing.
What goes wrong with a plain one
A normal QR code never changes. That gives you three problems.
Anyone can screenshot it and send it on. Whatever it unlocked yesterday still unlocks today. And nothing about it proves it came from you, so a printed sticker with a different code on it looks exactly as legitimate as yours.
For a link to a menu, nobody cares. For a ticket, a coupon or a door, somebody does.
Make it prove who made it
First fix the forgery. Don’t put the plain data in the code. Put the data and a signature.
You sign with a private key that only you hold. Anyone can check the signature with the matching public key, and nobody can make a new valid one without the private key. Change one character of the data and the signature stops matching.
The codes below work this way. The payload is small: what kind of code it is, when it was issued, when it expires, and a random ID. The signature is ECDSA on the P-256 curve, which comes to 64 bytes. The whole thing lands around 200 characters, which a QR code holds without trouble.
Signing doesn’t stop copying. A perfect copy of a signed code is still perfectly signed. That’s what the next two fixes are for.
Make it expire
Here’s a code that’s only good for 15 seconds, then replaces itself.
It’s the same idea as the six-digit code in an authenticator app. The expiry time sits inside the signed payload, so nobody can stretch it. Edit the time and the signature breaks.
The catch is a copy made right now. Someone photographs your screen and walks through the gate in the next 15 seconds. Expiry shrinks that window. It doesn’t close it.
Make it work once
So here’s a code that dies the moment someone uses it.
Each code carries a random ID, and a server remembers which IDs it has seen. The first scan gets in. Every later scan of that ID loses.
A signature can’t do this on its own. A signature only knows whether a code is genuine. It has no idea whether anyone has shown it to the gate already. Somewhere there has to be a record, and somebody has to decide who was first.
Which brings a cost with it. The scanner needs to reach that record. An offline gate can only check the signature and the expiry, and has to sync the used list later. There’s no way around that, only ways to be tidy about it.
Both together
Put them together and the code carries three claims. It’s ours, because of the signature. It’s recent, because of the expiry. And nobody’s used it, because of the ID.
A thief now has to get a live copy, in time, and get through before the person it was issued to. That’s a much harder job than forwarding a screenshot.
It still isn’t airtight. If the person who holds the real code hasn’t scanned yet, the copy wins, and the owner is the one turned away. The code can’t tell the two of them apart. It never could, because the code is the only thing in the exchange.
That’s where the rest of the system comes in. A gate that knows which person it expected, which door it is, and what time it is can ask more than whether the code is good. It can ask whether this event makes sense. We wrote about that in Identity + Quantity + Context = Truth. A signed code gives you the identity part. The reader supplies the rest.
Printed codes can’t rotate
Everything above assumed a screen. A label on a carton doesn’t change every 15 seconds.
For printed codes you give up expiry and keep the other two. Every item gets its own signed code with its own ID. The first scan is expected. A second scan of the same ID, from a different place, is the interesting one. A genuine label doesn’t get scanned twice in two cities. A photocopied one does.
That doesn’t prevent counterfeiting. It tells you it’s happening, and where. For most supply chains that’s the part they were missing.
If you’re printing at volume, the label stock and the printer matter as much as the cryptography. Our QR hardware and labelling pages cover what we use.
What it costs
Signing is the cheap part. The costs are elsewhere.
You have to keep a private key somewhere safe, and rotate it when someone leaves. A rotating code needs a clock the scanner and the issuer roughly agree on. A single-use code needs a connection, or a plan for when you don’t have one. And the more you put in the code, the denser the squares get, and the harder it is for a cheap camera to read in bad light.
For a link to your homepage, none of that is worth it. For anything someone would pay to fake, it usually is.
The demos here run on a small Cloudflare Worker. It holds the key, signs the codes and keeps the used list. The page you’re reading has no way to forge one. The public key is published at iotready.com/api/qr/key, so you can check a signature yourself without trusting us.
You can make your own, expiring or one-time, with our free QR code generator. No account, and we keep nothing about who made a code. If you want a code you can switch off after it’s printed, or one that stops after 50 scans, there’s a free account for that.