The licence system I didn't write
I sell a Windows app to print shops. Its licensing has no server, and a cracker who reads every byte of the executable still cannot forge a key. I did not write a line of it. Here is what I actually did, and the bug that silently rejected every key I ever issued.
I sell a Windows application called JerseyFactory-DTF to print shops. They paste in a team roster, it nests the names and numbers onto a sheet of film, and a job that took two and a half hours in CorelDRAW takes about two minutes. They pay for it. It runs on their machines today.
It has a licensing system with no licence server. Keys are cryptographically signed, bound to one machine, and cannot be forged by someone who pulls the executable apart and reads every byte inside it.
I did not write a line of that code. I built it with Claude Code, the way I build everything. I am saying that up front, because the interesting part of this story turned out not to be the cryptography. It was knowing what to ask for, and knowing when the answer I got back was wrong.
The constraint
A print shop's machine sits on a factory floor. It is often not on the internet, and even when it is, I did not want to build a thing that stops working the day I stop paying for a server. If I am still selling this in five years, the licence check has to work with the network cable pulled out.
So: no server. Which sounds simpler than it is, because the naive version of offline licensing dies immediately.
if (enteredKey == "JERSEY-2026-XYZ") {
unlock();
}Whatever the app has to know in order to check a key, an attacker also gets, because they have the app. Hide the string, obfuscate it, encrypt it: it does not matter. The app has to compare against something, and whatever that something is, it is sitting on their disk. One customer posts the key on a forum and the product is free forever.
The one idea that fixes it
The app must be able to check a key without being able to make one.
That is the whole thing, and it is what asymmetric cryptography is for. I hold a private key on a machine that never leaves my desk. It is the only thing in the world that can mint a licence. The app carries only the public half, which can verify a signature and can do absolutely nothing else.
A cracker can read every byte inside my executable, understand it perfectly, and still not be able to produce a working key. There is no secret in there to steal. That is the point.
The key a customer receives is a signature over a small payload: which product it is for, which machine it is bound to, when it expires, and who it belongs to. The app re-derives that payload on the customer's machine and asks one question: was this signed by the key I trust? Yes or no.
I am deliberately not publishing the exact hardware derivation or where the trial marker lives. Nothing in the security depends on those staying secret, but there is no reason to hand out a map either.
Three decisions the machine did not make for me
Claude wrote the code. It wrote what I asked for. Three of the things worth knowing about this system exist because I asked, and it would not have occurred to me to ask if I had not been the one selling the thing.
1. The product name goes inside the signed payload. I sell two products to the same print shops. If the signature only covers the machine and the expiry, then a customer holding a valid Jersey Factory Pro key could relabel it and unlock JerseyFactory-DTF, and the signature would still verify, because it was a real signature over real data. The product identity has to be inside the thing that got signed, or it is not protected at all. There are tests for both directions, because I got this wrong in my head first and only saw it when I tried to explain the key format out loud.
2. The update manifest is signed with the same key. The app checks for updates against a file hosted on GitHub. So I asked the uncomfortable question: what happens if someone steals my GitHub account? The honest answer, before this, was that they could point every one of my customers at whatever executable they liked. Now the app refuses any update whose signature it cannot verify. Someone who takes the account gets the account. They do not get my customers.
3. Deactivation is a signed code, not a database row. When a customer moves to a new PC, they click Deactivate and the app produces a signed kill code. They send it to me, I issue a key for the new machine. Deleting a row in my own records does not free up a licence. Only the code does. Which means the system does not depend on my database being correct, or on me being careful, and it lets a customer move machines at 11pm without me being awake.
None of those are clever cryptography. They are questions about my own business, asked in the right order. That is the part that does not come out of a model.
The bug that rejected every key I ever issued
The generator that mints keys is Python. The application that verifies them is C#. Both sides were correct. Every key I issued was rejected.
Python emits ECDSA signatures in DER encoding. .NET expects the raw `r‖s` concatenation. Same signature, same maths, two different ways of writing it down. The verifier looked at a perfectly valid signature, failed to parse it as the shape it expected, and returned false.
The reason this cost me a day rather than an hour is the failure mode. A rejected key produces exactly the same message as a customer typing their key in wrong. There is no stack trace, nothing crashes, nothing is broken in any way a log would show you. The system was working perfectly and confidently telling me the key was bad.
What eventually found it: I signed and verified inside a single process, in one language. It passed. So the maths was fine and the code was fine, and the fault had to live in the seam between the two — in the format, not the logic. The fix was one line telling .NET to expect the DER sequence.
When two sides of a boundary both insist they are correct, stop reading the code and go look at what is actually crossing the boundary.
There are now 23 automated tests around the licensing, and most of them exist because of that day.
So what did I actually do?
I did not write the code. I am aware of how that sounds, and I have thought about whether it makes this post dishonest to publish. I do not think it does, as long as I say it plainly, so: I say it plainly.
Here is what the job actually consisted of. Deciding there could be no server, and holding that line when a server would have been easier. Knowing that a string comparison was not good enough and why. Asking what happens when the same shop buys both of my products. Asking what happens if my GitHub account is stolen. Not trusting my own database. And, when everything was allegedly correct and nothing worked, knowing where to go looking.
A model will happily write you a licence check that compares two strings. It did not stop me and ask whether a Jersey Factory Pro key could be relabelled into a DTF key. Nobody asks that unless they are the one who has to answer the phone when it happens.
The code was the cheap part. It is genuinely the cheap part now, and pretending otherwise seems like a bad way to spend the next few years.
The part that is still not solved
None of this helps with the actual thing standing between me and a smooth first impression, which is that the installer is not code-signed. Windows SmartScreen greets every new customer with an unrecognised-app warning whose most obvious button is Don't run. Some print shops stop right there.
So the licensing is unforgeable, the updates survive an account takeover, and the thing that loses me sales is a certificate I have not bought yet. The last mile is always the thing. It is never the cryptography.