Security model
How device locking and response signing actually work, and — just as important — what they don't protect against.
Device fingerprint (HWID)
Each SDK computes a SHA-256 hash of a platform-stable machine identifier. Only the hash is ever sent to the server — the raw identifier never leaves the device.
| OS | Source |
|---|---|
| Windows | MachineGuid under HKLM\SOFTWARE\Microsoft\Cryptography |
| Linux | /etc/machine-id (falls back to /var/lib/dbus/machine-id) |
| macOS | IOPlatformUUID, from ioreg -rd1 -c IOPlatformExpertDevice |
HWID = sha256(trim(source_value)), lowercase hex, 64 characters.
A device running multiple of your tools presents the same HWID to all of them — that's expected, the identifier is machine-wide, not per-product. Also expected: cloned VM images or containers without a persisted machine-id will produce colliding or unstable HWIDs across "different" machines. This is a known, structural limitation of any machine-fingerprinting approach, not something a licensing server can detect or fix.
Response signatures
Every /activate and /verify response — success or failure — is signed with Ed25519. The private key never leaves the server; only the public key (shown on the guide page) is embedded in your SDK, so a response can't be forged offline even by someone who has fully decompiled your tool and extracted that public key.
To verify a response yourself
- Take the
payloadobject from the response. - Canonicalize it: sort all object keys recursively, then JSON-serialize with no extra whitespace (equivalent to JavaScript's
JSON.stringify(sortKeysDeep(payload))). - Base64-decode the
signaturefield. - Verify the canonical bytes against that signature using the Ed25519 public key.
All four SDKs implement this already — you only need it if you're writing your own client from scratch. A subtle but important detail: don't re-serialize the payload with a JSON library that escapes differently than the above (e.g. escaping </>/&, or non-ASCII characters) — the signature won't verify if your bytes don't match exactly what the server signed.
Offline grace period
Every signed response includes issued_at and offline_grace_until (72 hours later). If a live check fails (no network, server down), SDKs fall back to the last cached signed response and treat the license as valid only if it was valid and the cache is still within its grace window. This is a resilience feature — it keeps your tool working through a brief outage — not a security boundary: a user can roll their system clock back to keep extending the grace window indefinitely if they're determined to. If your product genuinely requires hard, continuous online enforcement, don't implement offline grace in your integration — treat any failed live check as invalid.
Key format
Keys look like 7K2M9-QX3R8-2VBN5-8JH4C-T — 4 groups of 5 characters from a 32-symbol Crockford base32 alphabet (excludes easily-confused I/L/O/U), plus a trailing checksum character. ~100 bits of entropy. The checksum is public and documented purely to catch typos client-side before hitting the server — it adds no forgery resistance, and isn't meant to.
Limits of client-side protection
This system is server-authoritative and cryptographically signed, which stops casual piracy and key-sharing effectively. It cannot stop an attacker with full control of the machine running your tool: they can patch out the license check in your binary, run it under a debugger and skip the check, or replace the SDK code entirely before your program ever calls it. This is true of every client-side license check that has ever existed, for any product, from any vendor — it's a structural limit of running your enforcement logic on hardware you don't control, not a gap specific to this implementation.
If you need more
Realistic ways to raise the bar further, roughly in order of effort:
- Call
/verifyrepeatedly during runtime, not just at startup — makes a one-time patch-and-run bypass less durable. - Obfuscate or strip debug symbols from your release builds so the check is harder to locate.
- Gate real functionality behind the server, not just a boolean check — e.g. have the server return a value your tool actually needs (a decryption key, a config blob) rather than just "yes/no". A patched-out boolean check is much easier to bypass than a missing dependency.
- Commercial anti-tamper/obfuscation tooling if piracy resistance is business-critical — outside the scope of what a licensing server itself can provide.