Translate

How to Generate Software License Keys and Verify Them


Protecting your software with license keys is a common way to control distribution, enable paid features, and prevent casual piracy. A good licensing system balances security with user convenience: keys should be hard to forge but straightforward to issue and verify, and verification should work both online and offline where appropriate.

This guide explains practical, secure approaches to generate and verify software license keys, covering license formats, cryptographic signing, online activation, offline verification, revocation, storage patterns, and implementation examples you can drop into your backend and client apps.


Overview: Licensing Models and Requirements

Before building anything, decide what you need from a license system:

  • License types

    • Perpetual (one-time purchase)

    • Time-limited (trial, subscription expiry)

    • Feature-flag (enables specific features)

    • Seat-based / concurrent-user

    • Machine-bound or user-bound

  • Verification modes

    • Online activation: client contacts server to validate/activate key.

    • Offline verification: client verifies key cryptographically without server contact (useful for air-gapped environments).

    • Hybrid: offline-friendly signed keys + optional server activation for revocation/usage tracking.

  • Security goals

    • Make keys hard to forge.

    • Allow revocation and updates.

    • Minimize exposure of private signing keys.

    • Keep verification fast and reliable.


Key Design Choices

  1. Key format

    • User-friendly: grouped alphanumeric blocks (e.g., ABCD-EFGH-IJKL-MNOP).

    • Encoded payload vs. random token:

      • Encoded: contains metadata (expiry, features, user id) and is signed.

      • Random token: opaque string stored on server mapped to license details.

  2. Verification strategy

    • Server-backed tokens: server stores token → verification = server lookup (simpler, revocable, requires online).

    • Signed tokens: token includes data + digital signature → offline verification is possible, revocation requires server interaction or short lifetimes.

  3. Cryptography

    • For signatures: use asymmetric crypto (RSA or ECDSA). Keep the private key on the server only.

    • For MACs/HMAC: symmetric option (HMAC-SHA256) but then the verification secret must be embedded in clients → less secure for distributed software.

    • Prefer asymmetric signatures for offline verification.

  4. Revocation

    • Server-side blacklists/activation counts (works for server-backed or activated signed keys).

    • Short-lived license tokens or periodic revalidation (forces clients to check the server sometimes).


Recommended Approach (Hybrid)

  1. Generate a signed license token containing:

    • License ID

    • License type (perpetual/time-limited/feature flags)

    • Issued timestamp

    • Expiry timestamp (if applicable)

    • Optional: bound machine fingerprint or user identifier

  2. Sign the token using server private key (RSA/ECDSA).

  3. Ship the token to the customer as a compact string (Base64 or Base32) grouped for readability.

  4. In the client:

    • Verify the signature using the public key (offline).

    • Validate expiry and payload fields.

    • Optionally, on first use, call activation endpoint to register the installation (server records activation and optionally returns revocation status).

  5. To revoke, mark license in server DB and either:

    • Have clients re-check periodically, or

    • Require online activation to use the product.

This gives the best mix of offline capability and server control.


Token Structure Examples

Two common patterns:

1) Encoded JSON signed with RSA (compact)

  • Payload (JSON):

    { "id": "LIC-00012345", "type": "pro", "user": "alice@example.com", "exp": 1710000000, "features": ["featureA", "featureB"], "bind": "HWID:abc123" // optional }
  • Compact flow: Base64URL(payload) + '.' + Base64URL(signature)

  • Very similar to JWT but you can use your own compact format.

Example final key (for user display) could be Base32 of the whole token and grouped like:
ABCD-EFGH-IJKL-MNOP-QRST-UVWX

2) Random token stored on server

  • Token: 6b2f3d9a-... (UUID or crypto-random string)

  • Server DB holds the details (expiry, user, features).

  • Client sends token to server for verification/activation.


Implementation Examples

Below are straightforward examples to generate and verify signed license tokens using asymmetric keys. Pick the language closest to your stack. These examples focus on clarity, not production-level error handling.

Example: Server-side generation (Python, RSA signature)

# server_generate.py import json import base64 import time from Crypto.PublicKey import RSA from Crypto.Signature import pkcs1_15 from Crypto.Hash import SHA256 from Crypto.Random import get_random_bytes # Load or generate RSA key pair (store private key securely!) key = RSA.generate(2048) private_pem = key.export_key() public_pem = key.publickey().export_key() # Save keys to files (do this once and secure private key) with open("private.pem", "wb") as f: f.write(private_pem) with open("public.pem", "wb") as f: f.write(public_pem) def create_license(payload: dict) -> str: payload_json = json.dumps(payload, separators=(',', ':'), sort_keys=True).encode() h = SHA256.new(payload_json) signature = pkcs1_15.new(key).sign(h) token = base64.urlsafe_b64encode(payload_json).rstrip(b'=').decode() + '.' + base64.urlsafe_b64encode(signature).rstrip(b'=').decode() return token payload = { "id": "LIC-00012345", "type": "pro", "user": "alice@example.com", "iat": int(time.time()), "exp": int(time.time()) + 60*60*24*365, # 1 year "features": ["export", "sync"] } license_token = create_license(payload) print("License token:\n", license_token)

Example: Client-side verification (Python)

# client_verify.py import json import base64 import time from Crypto.PublicKey import RSA from Crypto.Signature import pkcs1_15 from Crypto.Hash import SHA256 def urlsafe_b64decode_nopad(data: str) -> bytes: padding = '=' * ((4 - len(data) % 4) % 4) return base64.urlsafe_b64decode(data + padding) def verify_license(token: str, public_key_pem: bytes) -> dict: payload_b64, sig_b64 = token.split('.') payload_json = urlsafe_b64decode_nopad(payload_b64) signature = urlsafe_b64decode_nopad(sig_b64) # Verify signature key = RSA.import_key(public_key_pem) h = SHA256.new(payload_json) pkcs1_15.new(key).verify(h, signature) # raises ValueError if invalid payload = json.loads(payload_json.decode()) now = int(time.time()) if "exp" in payload and payload["exp"] < now: raise Exception("License expired") return payload with open("public.pem", "rb") as f: pub = f.read() token = "<paste_token_here>" payload = verify_license(token, pub) print("Verified payload:", payload)

Notes:

  • Use pycryptodome or a similar library.

  • Keep public.pem in your client app distribution. Keep private.pem only on the server.


Example: Server-side generation + verification (Node.js, ECDSA P-256)

Using ECDSA (smaller keys, faster):

// server_generate.js const crypto = require('crypto'); function generateKeyPair() { return crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }); } const { privateKey, publicKey } = generateKeyPair(); // Export PEM if you want to save them const privatePem = privateKey.export({ type: 'pkcs8', format: 'pem' }); const publicPem = publicKey.export({ type: 'spki', format: 'pem' }); console.log(publicPem); function createLicense(payload) { const payloadJson = Buffer.from(JSON.stringify(payload)); const sign = crypto.createSign('SHA256'); sign.update(payloadJson); sign.end(); const signature = sign.sign(privateKey); const token = payloadJson.toString('base64url') + '.' + signature.toString('base64url'); return token; } const payload = { id: "LIC-0005678", type: "trial", iat: Math.floor(Date.now()/1000), exp: Math.floor(Date.now()/1000) + 3600*24*30 // 30 days }; console.log(createLicense(payload));

Client verification in Node.js is analogous: use crypto.createVerify('SHA256') with the public key and verify.


Activation Server Flow (Online Activation)

If you prefer server-backed activation:

  1. User inputs license key in the client.

  2. Client sends POST to https://license.example.com/activate with:

    • license key

    • client id (installation id)

    • optional machine fingerprint

  3. Server validates the key (check DB or verify signature), checks activation limits, and responds:

    • success + activation ID + expiration + allowed features

    • or failure + reason (invalid, expired, revoked, activation limit reached)

  4. Server stores activation record (license_id, client_id, timestamp, IP).

  5. Client stores activation token locally (encrypted) and uses it for offline checks.

  6. For deactivation, client calls https://license.example.com/deactivate.

Advantages:

  • Central control, easy to revoke, and monitor usage.

  • Can implement trial-to-paid conversion flows.

Security tips:

  • Use HTTPS (TLS).

  • Authenticate server responses (sign responses or use short-lived tokens).

  • Rate-limit activation endpoints to prevent brute-force.


Offline Verification Considerations

  • Use signed tokens so the client can verify signature using the public key.

  • Avoid embedding secret keys in clients.

  • If binding to a machine, include a hardware fingerprint in the payload and sign it. On client-side, compute the same fingerprint and compare.

  • To handle revocation in offline mode:

    • Use short-lived tokens and require periodic online re-validation.

    • Maintain a small revocation list (CRL) fetched occasionally and stored on client; check the license ID against the CRL.


Formatting License Keys for Humans

Raw Base64 or JSON is ugly. Convert tokens to readable keys:

  • Encode token bytes in Base32 or Base58 and group into blocks:
    XXXX-XXXX-XXXX-XXXX

  • Optionally include a short checksum (e.g., CRC32 or truncated HMAC) to detect typos before attempting verification.

Example generation flow:

  1. Create signed token bytes.

  2. Compute Base32 string.

  3. Insert dashes every 4 or 5 characters for readability.

  4. Optionally append a 4-character checksum.

Be careful: avoid leaking sensitive payloads in human-readable keys. If the token contains user-sensitive data, prefer encryption or use opaque server tokens.


Database Design for Server-Backed Keys

If you store tokens on server:

  • licenses table

    • id (UUID)

    • key (hashed or token id)

    • user_id

    • type

    • features (JSON)

    • issued_at

    • expires_at

    • revoked (bool)

    • metadata

  • activations table

    • id

    • license_id

    • client_id (installation id)

    • hwid (optional)

    • ip_address

    • activated_at

    • last_seen_at

    • status

Hash storage:

  • Never store raw license tokens in plaintext if they act like passwords. Store hashed tokens (use HMAC or a slow KDF if necessary) to reduce risk of database leakage enabling forgery.


Security Best Practices

  • Use asymmetric signatures for offline verification.

  • Keep private keys offline and rotated periodically.

  • Use proper key management (KMS like Google KMS, AWS KMS) for private keys.

  • Limit activation rates and add CAPTCHA/anti-automation if needed.

  • Enforce TLS (HTTPS) for all network traffic.

  • Sanitize and validate all inputs server-side.

  • Log activation attempts with rate-limiting and alert on suspicious patterns (e.g., many activations from different IPs).

  • Use short-lived tokens for trial licenses or include revalidation period.

  • Implement graceful fallback for users who cannot reach activation servers (allow time-limited offline use).


UX Considerations

  • Make the activation UX simple: paste or click-to-paste license key fields, meaningful error messages (not generic "invalid key"), friendly help links.

  • Provide clear expiry information and renewal instructions.

  • Allow license transfer or deactivation from the user account page.

  • Offer a way to handle legitimate offline users (support ticket or phone activation).


Example End-to-End Flow (Practical)

  1. Purchase:

    • User buys license through e-commerce; server creates license record with a unique ID and payload.

  2. Issuance:

    • Server creates signed token (payload + signature), encodes it as user-friendly key, and emails it.

  3. Activation:

    • First run: user enters key in client. Client verifies signature offline and shows allowed features.

    • Client calls server activation endpoint with installation id to register activation. Server checks activation limits and marks activation record.

  4. Use:

    • Client runs offline and verifies signature + expiry locally.

    • Periodically (e.g., monthly), client attempts to revalidate online to detect revocation.

  5. Revocation / Renewal:

    • If a license is refunded or abuse detected, server marks it revoked. Clients that revalidate will be told to deactivate or downgrade.


Troubleshooting & Common Pitfalls

  • Clients reject valid keys: check exact menu names, character set, padding in Base64URL decoding, clock skew (use iat/exp with leeway).

  • Activation race conditions: ensure DB transactions prevent over-allocating seats.

  • Private key compromise: immediately rotate keys and invalidate previously issued tokens, or use short-lived tokens to limit exposure.

  • Long keys frustrate users: shorten by deriving a user-friendly token that maps to server records, or use grouped Base32.


Final Notes

A robust licensing system requires trade-offs between security and usability. For most applications:

  • Use signed tokens (asymmetric) to allow offline verification.

  • Provide optional server activation for revocation and tracking.

  • Keep private keys secure and use KMS where possible.

  • Make keys readable and UX-friendly.

  • Balance license lifetime and revalidation frequency based on your user base.

Start with a minimal secure design (signed tokens + optional activation) and iterate as you learn real-world usage patterns. That gives you the flexibility to tighten controls (shorter lifetimes, stricter activation rules) without disrupting legitimate users.