License key generator for .NET: why you shouldn't roll your own
A real .NET license system isn't a random key string — it's a signed license verified against an embedded public key, with expiry, node-lock, entitlements and revocation. Here's why a homegrown key generator fails and what a proper signed-license system looks like.
If you searched for a ”.NET license key generator,” the honest answer is that you don’t want a key generator — you want a signed license system, and the two are not the same thing. A generator produces a key string your app validates by its shape, which means the logic that decides “this key is valid” is the same secret an attacker extracts to build a keygen. A real license is a small signed document: the server signs it with a private key that never ships, your app verifies the signature against an embedded public key, and forgery becomes cryptographically impossible. This post explains why the homegrown route fails and what the proper version looks like in .NET.
What people mean by “key generator” — and why it’s the wrong mental model
The classic design goes like this: generate keys in a recognizable pattern (grouped characters, a checksum digit, maybe a segment derived from the customer name), hand one to each buyer, and have the app accept any key that matches the pattern. It feels like security because the keys look official.
It isn’t, and the reason is structural. If your app can decide a key is valid by inspecting the key itself, then everything needed to make a valid key is inside your app. A decompiler turns your validation routine into a generation routine — that’s literally what a “keygen” is. Because .NET assemblies decompile cleanly back to readable C#, this isn’t a theoretical risk; it’s a first-afternoon result for anyone who bothers.
And even if the format were somehow un-guessable, a format-based key has no answers to the questions that actually run a software business:
- How does it expire? A subscription needs an end date the key carries and the app enforces.
- How is it bound to a machine? Nothing stops one key being pasted onto a thousand installs.
- How do you unlock different features per plan? The key is opaque; it can’t say “this customer gets export but not the API.”
- How do you kill a leaked or refunded key? You can’t recall a string that’s already in the wild.
A generator produces none of that. It produces a string.
What a real license is: a signed document
Replace “generate a key” with “sign a license.” The unit isn’t a random string — it’s a small payload of facts (licensee, product, tier, seats, expiry, entitlements) with a cryptographic signature attached.
Here’s the asymmetry that makes it work. Your server holds an RSA private key and uses it to sign the payload. Your app embeds only the matching public key. A public key can verify that a signature matches a payload, but it can never create one. So:
- Decompile the app, read the public key, publish it on a billboard — an attacker still can’t forge a license, because forging requires the private key that never left your server.
- Change one byte of the payload — bump
tierfromfreetopro— and the signature no longer matches. Validation fails.
This is the exact mechanism behind TLS and code signing, pointed at licensing. In .NET the primitives ship in the box (System.Security.Cryptography.RSA), and verifying a signature is a few lines. Which is precisely the trap: the signature check is easy, so people assume the whole system is easy, and it isn’t.
The parts a signature alone doesn’t give you
A valid signature proves the license is genuine and unaltered. It does not, by itself, do any of the following — and a real system needs all of them:
Expiry and clock-tamper defense
A subscription or trial carries an expiry the app compares against the current time. That immediately invites the obvious attack — roll the clock back and a trial never ends — so offline validation has to remember the latest time it legitimately saw and treat a big backward jump as tampering. Keyright’s SDK does this: moving the clock back beyond a ClockTamperToleranceHours window (default 24h) on a time-limited license yields a ClockTampered status and refuses until the time is corrected. Perpetual licenses aren’t subject to it.
Node-locking
Binding a license to a machine needs a stable fingerprint with tolerance, so a customer who swaps a NIC or disk isn’t locked out. Too strict and you generate support tickets; too loose and the lock is meaningless. Keyright derives the fingerprint and applies a configurable NodeLockTolerance (default 1).
Entitlements
Shipping one binary that unlocks different capabilities per plan means the license has to carry those capabilities. Keyright bakes an entitlement template into each tier — named flags ("export": "true") and numeric limits ("max-projects": "10") — and the SDK reads them locally: IsEnabled("export"), GetLimit("max-projects", fallback: 1). Gate features on entitlements, not on a hard-coded tier check, and changing a plan doesn’t mean shipping new code.
Revocation
A leaked, refunded, or charged-back license has to die — and a signed payload issued last year knows nothing about a refund last week. Online, Keyright revokes a key server-side and the client drops to free on its next lease refresh. Offline, it can ship a signed revocation list your app honors with no network call. A format-based key generator has no revocation story at all.
What the proper .NET flow looks like
With Keyright the “generator” is replaced by a server that signs, and your client verifies. Every tenant gets its own isolated RSA key pair; the private half is stored AES-256-GCM-encrypted server-side and never exposed, so your signatures never share a key with anyone else. Your app embeds only the public half:
var client = KeyrightClient.Initialize(new KeyrightOptions {
Product = "acme-app",
PublicKeyBase64 = "MIIBIjANBgkq...", // not a secret — ships in your binary
ServiceUrl = "https://keyright.delta1labs.com",
});
// Offline: verify the signature, product, node-lock, expiry — no network, never throws.
LicenseInfo info = client.Validate();
if (info.IsPaid && client.IsEnabled("export")) { /* unlock the feature */ }
Validate() verifies the signature against the embedded public key, checks node-lock and expiry, honors any shipped revocation list, and fails closed — any problem resolves to the free edition carrying a Status (SignatureInvalid, Expired, Revoked, MachineMismatch, …) rather than throwing or, worse, unlocking. For seat enforcement and prompt revocation you add one online ActivateAsync(key) call, which exchanges the key for a machine-bound lease.
Be honest about the limit
Signing kills forgery dead — nobody keygens a license they can’t sign. But the code that reads the result still runs on the user’s machine, and the branch that unlocks a feature can be patched out by a determined attacker with a decompiler. That’s not an argument against signing; it’s the reason you pair it with two things: online activation, so seats and revocation are enforced on a server the attacker doesn’t control, and obfuscation, so patching the client gate is expensive. Signing defeats the forger; the rest defeats the patcher.
Skip the generator, ship the system
The takeaway isn’t “you can’t write an RSA verify” — you can, in an afternoon. It’s that a license system is signing plus expiry, node-lock, entitlements, revocation, seats, and a fail-closed client, and a random key generator gives you none of it while looking like it does. Keyright is that system, built: per-tenant RSA signing, offline verify, entitlements, and revocation, with a .NET-first SDK. The getting-started guide walks the full path from an empty workspace to a shipping licensed app, and the free plan covers real licensing before you pay a cent.
Try Nebula.NET
Harden your .NET code in minutes — start with the free edition.