Hardening
Offline verification is fundamentally crackable. A determined attacker can patch your binary or swap the embedded key. Hardening does not make you uncrackable. It raises the cost and buys time, often the valuable launch window, so that cracking is not worth the effort for your market.
Clock-rollback defense
Section titled “Clock-rollback defense”A determined user can set their clock back to dodge expiry. Keep a sealed, monotonic high-water mark of the latest time you have seen (HMAC it so it cannot be edited), and reject wall-clock times earlier than the mark. Treat a rollback as grace expiry rather than a hard crash, to avoid false positives from DST, dead CMOS batteries, and VM snapshots.
The SDK’s evaluateOffline and the JUCE kit both implement this. If you write your own verifier, replicate it. See
the offline gate for what persisting the mark to several locations buys.
Principle: centralize truth, sprinkle checks
Section titled “Principle: centralize truth, sprinkle checks”Do not duplicate the verification logic: that costs maintenance and adds bugs, and bugs here lock out paying customers. Do call the cheap offline verify from many independent, inlined sites. A local file read plus one Ed25519 check is sub-millisecond with no network, so you can afford to run it often.
A single cached licensed boolean is a single point of failure: one byte to flip, one branch to patch. Back it with several
independent verifiers.
Patterns that work
Section titled “Patterns that work”- Multiple inlined check sites. Re-verify at editor open, on state or preset load, on selected parameter changes, and on
a jittered timer every few minutes. Inline each check; do not route them all through one
isLicensed()function, which is one patch point again. - Delay and vary the consequence. The amateur tell is “check fails, instant silence”. The cracker sees the check, sees the symptom, links them, and patches once. Instead, let it pass, then minutes later and probabilistically degrade: an occasional short noise burst, a slow gain droop, a corrupted save. Now the failing check and its symptom are far apart and hard to correlate.
- Audio code reads a flag set by many verifiers. You cannot verify in
processBlock. Read astd::atomic<bool>there, but have several independent verifiers set it, with several different degradation behaviors, so there is no single store to patch. - Binary integrity and signing. Checksum your own binary at runtime and code-sign (Apple notarization, Windows Authenticode), so patched binaries are detectable and do not load cleanly.
- Protect the embedded key. Do not store the public key as one clean contiguous constant. Split or obfuscate it so a cracker cannot trivially swap in their own.
What not to do
Section titled “What not to do”- Never check in the audio callback. No I/O, locks, or allocation on the audio thread, ever. Only an atomic read.
- Do not fail instantly and loudly. See pattern 2.
- Do not go full Denuvo. Heavy obfuscation, anti-debug, and VM packing is a specialised, expensive, ongoing arms race, and naive attempts lock out legitimate users, the fastest way to wreck your reviews. Moderate, well-tested hardening plus online mode beats fragile heroics.
- Fail open on ambiguity, closed only on proof. A hardening check that locks out a paying customer is worse than none.
The honest ceiling
Section titled “The honest ceiling”Every offline scheme is eventually cracked. Measure success in attacker hours and days to first crack, not “uncrackable”. For high-value or subscription products, prefer online mode. For offline products, apply moderate hardening and lean on short leases and server-side caps.