Choosing a signature algorithm for your license keys: Ed25519, ECDSA and RSA compared
A Keyright license key is not a password and not a random token. It is a short document — the customer, the tier, the seat count, the expiry — wrapped in a digital signature your application verifies offline against a pu
A Keyright license key is not a password and not a random token. It is a short document — the customer, the tier, the seat count, the expiry — wrapped in a digital signature your application verifies offline against a public key it ships with. The signature is the whole security story: it is why a customer cannot edit "Standard" to "Pro" in a key and have it still validate, and why nobody can mint a key you did not issue.
Which raises a question that sounds like an implementation detail and is not: which signature algorithm? The honest answer is that this choice ripples out into three things your customers and your support team feel directly — how long the key is, how fast every launch verifies it, and how badly a single bug in your issuing code can hurt you. This post weighs the three realistic options — RSA, ECDSA and Ed25519 — on exactly those axes, and explains why Keyright signs with Ed25519 by default.
What actually ends up in the key
Start from the shape of the key, because it is where the size argument lands. A license key is the canonical payload plus a signature over it, encoded into characters a human can type:
key = base32( canonical_payload_bytes || signature_bytes )
The payload is small and roughly fixed — a few dozen bytes of claims. The signature is the variable, and for most algorithms it is the single largest thing in the key. So the raw byte length of a signature is, to a first approximation, the length of your key. That is the lens to read the next section through: every byte of signature becomes about 1.6 base32 characters your customer has to type or paste without a typo.
RSA: it works, and it is the wrong shape for a key
RSA is the algorithm everyone reaches for first, because it is the one they learned. For license signing it is defensible and also the weakest fit of the three.
The size problem is immediate. An RSA-2048 signature is exactly 256 bytes — the modulus size — no matter how small your payload. Encode that to base32 and you have added roughly 410 characters to the key before the claims. A key that long is not something a customer types; it is something they paste, and if it ever has to be read aloud, printed on a card, or entered on a device without a clipboard, you have already lost. Pushing RSA to the 128-bit security level that the elliptic-curve options reach means RSA-3072, whose signature is 384 bytes, which only makes the key worse.
RSA does have one genuine strength worth naming honestly: verification is fast, because it uses a tiny public exponent (65537), so the client-side check — the operation that happens on every launch — is cheap. But ECDSA and Ed25519 verify in well under a millisecond too, so this advantage buys you nothing a user can perceive, and it comes attached to a signature four times the size.
In .NET the verify side looks like this:
using var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(publicKeyDer, out _);
bool ok = rsa.VerifyData(
payload, signature,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
Nothing is wrong with that code. The problem is the 256 bytes sitting in signature, and that no amount of encoding cleverness makes them smaller.
ECDSA: the right size, with a nonce you can get wrong
Elliptic-curve DSA over the NIST P-256 curve gives you 128-bit security — comparable to RSA-3072 — with a raw signature of just 64 bytes (two 32-byte integers, r and s). That is a quarter the size of RSA-2048 for a stronger key, and it is built directly into System.Security.Cryptography on every supported .NET version, which makes it the obvious choice if your constraint is "only in-box algorithms":
using var ecdsa = ECDsa.Create();
ecdsa.ImportSubjectPublicKeyInfo(publicKeyDer, out _);
// Keyright stores the raw r‖s form to keep the key short; .NET verifies it directly.
bool ok = ecdsa.VerifyData(
payload, signature,
HashAlgorithmName.SHA256,
DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
Note that last argument. ECDSA signatures have two encodings: the compact 64-byte IEEE P1363 concatenation of r and s, and the ASN.1 DER form, which wraps them in a SEQUENCE of two INTEGERs and runs 70–72 bytes with variable length (leading-zero rules make DER signatures differ in size signature to signature). For a license key you want the fixed 64-byte form; a variable-length DER signature makes your key length wobble and complicates fixed-width parsing. Getting this wrong is a common interop bug — a signer that emits DER and a verifier that expects P1363 will reject every valid signature.
The deeper issue with ECDSA is not size, it is the nonce. Every ECDSA signature consumes a per-signature secret value k. If k is ever reused across two different messages, or is even slightly predictable, the private key falls out of the two signatures with a few lines of algebra. This is not a hypothetical — it is the exact mechanism behind several famous key extractions. .NET's ECDsa generates k correctly and keeps it internal, so in-framework signing is safe. But the moment your issuing path involves custom code, a different language, or a borrowed RNG — and a licensing backend often grows exactly that — ECDSA is one weak random source away from leaking the key that protects your entire product. The algorithm hands you a loaded footgun and trusts you to keep it pointed away.
Ed25519: small, fast, and nothing to get wrong
Ed25519 — EdDSA over the Curve25519-derived Edwards curve — was designed with the lessons of RSA and ECDSA baked in, and for license keys it wins on every axis that matters.
The sizes are the best of the three: a 64-byte signature and a 32-byte public key, with 128-bit security. The public key matters too, because it is the value your application embeds and (if you harden it) pins; a 32-byte anchor is far easier to fingerprint and protect than RSA's ~294-byte SubjectPublicKeyInfo. Its DER public key begins with the fixed prefix MCowBQYDK2Vw in base64, a giveaway that you are looking at an Ed25519 key.
Crucially, Ed25519 is deterministic. The per-signature nonce is derived by hashing the private key together with the message, so it is unique per message without needing any randomness at signing time at all. There is no k to reuse, no RNG to misconfigure, no nonce bias to leak the key — the single sharpest edge of ECDSA is simply absent. Signing the same payload twice produces byte-identical output, which also makes issuing idempotent and test fixtures stable. Verification is constant-time by construction, sidestepping a class of timing issues you would otherwise have to reason about.
The one practical catch on .NET: Ed25519 is not exposed through the in-box System.Security.Cryptography surface the way RSA and ECDsa are, so verification goes through a bundled implementation rather than the BCL. From an integrator's point of view that is invisible — the Keyright SDK verifies for you:
var verifier = new LicenseVerifier(new LicenseVerifierOptions
{
// 32-byte Ed25519 public key, base64 SubjectPublicKeyInfo — safe to ship, worth pinning.
PublicKeyBase64 = "MCowBQYDK2VwAyEA9mF3rQ2pxK8vN1cWbYh6Zt0uQ4sJ7eD3rL5nX8kH2A=",
});
LicenseResult result = verifier.Verify(licenseKey);
if (result.IsValid && result.Claims.Tier == "Pro")
{
// honor the entitlements
}
The 64-byte signature keeps the key short, the deterministic design keeps your issuing side out of the nonce minefield, and the verify call is a sub-millisecond operation you can run on every launch without a server in the path.
How the three land on the axes that matter
Read the table the way a licensing system experiences it. The row that decides whether your key is usable at all is key length, and it eliminates RSA for anything a human touches. The row that decides whether a single bad day in your issuing code costs you the whole product is nonce hazard, and it separates Ed25519 from ECDSA. On the two rows where RSA is competitive — determinism and verify speed — the elliptic-curve options match or beat it anyway. There is no axis on which RSA-2048 is the right answer for license keys, and the only axis on which ECDSA beats Ed25519 is "already in the box on every old target."
What this means in practice
If you are starting fresh, sign with Ed25519. It produces the shortest keys, reaches a 128-bit security level, verifies fast enough to run on every launch, and — the part that matters most when your signing path grows beyond one tidy function — it has no per-signature nonce to leak the private key. That is why it is Keyright's default: the SDK embeds a 32-byte public key you can pin, verifies the 64-byte signature offline, and never asks your issuing side to supply randomness it might get wrong.
Reach for ECDSA P-256 only when a hard constraint forces you to in-box algorithms on a target where Ed25519 is not available — and if you do, pin the fixed 64-byte IEEE P1363 encoding, let the framework generate the nonce, and never let a custom signer anywhere near k. Reach for RSA only when you are interoperating with a system that already mandates it, and accept that your keys will be paste-only.
The signature algorithm is the foundation the entire licensing scheme stands on. Pick the one that is small enough to be typed, strong enough to last, and shaped so that the easiest path through your own code is also the safe one.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.