To license a commercial Python app: issue license keys to customers, validate each key on your own server with a signed response the client can't forge, optionally bind a key to a device (hardware ID) and add expiry/trials, then deliver a protected build. The golden rule — a license check that runs entirely on the customer's machine can always be bypassed, so the real decision has to live on a server you control.
Once you've protected the source of an app you're selling, there's a second problem obfuscation doesn't touch: making sure only paying customers can actually run it. That's *licensing* — keys, trials, expiry, per-seat or per-device limits — and it's a different job with a different failure mode.
The failure mode is worth seeing up front, because almost every first attempt gets it wrong in the same way. Here's a tested look at why a license check on the customer's machine can never be the real gate, and what to do instead.
Licensing and obfuscation are two different jobs
Obfuscation hides how your code works — it renames identifiers and encrypts strings so a buyer can't read your logic. Licensing controls who may run it — trials, expiry, seat counts, revocation. You need both to sell software, but neither substitutes for the other: perfectly obfuscated code with no license is a free product, and a license check in plain, readable source is trivially removed. If you haven't protected the source yet, start with the obfuscator and the protect-before-selling pipeline.
Why a client-side license check never holds (tested)
Here's the check nearly everyone writes first — the key comparison runs on the customer's machine:
def check_license(key):# Runs entirely on the customer's machinereturn key == "REAL-KEY-9931"def premium_report():return "Premium report: revenue=88240.10"def main(user_key):if check_license(user_key):print(premium_report())else:print("License invalid — upgrade to unlock.")
It works as intended for honest users: the right key unlocks the feature, the wrong key is blocked.
main("REAL-KEY-9931") -> Premium report: revenue=88240.10main("WRONG") -> License invalid — upgrade to unlock.
But the attacker controls the runtime. They don't need your key — they just replace the check. One line, and the premium feature unlocks with no valid license at all:
import licensed_applicensed_app.check_license = lambda k: True # override the checklicensed_app.main("anything")-> Premium report: revenue=88240.10
Obfuscation makes this harder to *find* — but not impossible, because the check still runs on a machine the attacker owns. Any license decision made entirely on the client can be forced. The gate has to live somewhere the customer can't edit: your server.
What real licensing needs
A licensing system that actually holds up has a few moving parts:
- License keys — a unique key per customer or seat that you can look up, count, and revoke.
- Server-side validation — the app asks *your* server whether a key is valid and verifies a signed response (e.g. Ed25519), so a fake server or a patched client can't forge a "valid" answer.
- Hardware binding — tie a key to a device fingerprint so one license can't be shared across a whole team.
- Expiry and trials — time-limited keys for subscriptions and free trials, enforced by the signed server response, not a local clock.
- Revocation — turn off a key server-side when a refund or chargeback happens.
Building all of that yourself is real work — key generation, a signing scheme, a validation server, an offline path. Our sister project [Licers](https://licers.com) provides it as a drop-in for Python: pip install pylicensify, add a few lines, and you get device-bound, server-validated licensing with Ed25519-signed responses a fake server can't forge — keys, expiry and hardware locking included.
import pylicensify# Asks your Licers server and verifies the signed response —# the client can't fake a valid result.status = pylicensify.verify(key=user_key, hardware_id=True)if status.is_valid:run_app()else:prompt_for_license()
For high-value features, keep the crown jewels on your server
Server-validated licensing stops key forgery, but note the subtle limit in the demo above: if premium_report() is computed on the client, an attacker who bypasses the check still has your code and can run it. The strongest model for genuinely valuable functionality is to keep that computation or data on a server you control, and deliver its results only to a validated license. Then a cracked client gets an empty shell — the value was never shipped.
Layer it by value: obfuscate everything, license everything server-side, and for the one or two features that are your real moat, run them server-side entirely. Match the effort to what a buyer would pay to steal it.
Offline activation — the practical middle ground
Not every customer is always online, and some enterprise or air-gapped deployments never are. The usual answer is a signed offline token: you issue a license file that's cryptographically signed for a specific machine and validity window, the app verifies the signature locally (it can check a signature without trusting the client's *decision*), and it re-validates online when it next can. This keeps offline users working without turning the local check back into the weak point.
Package and deliver the protected build
With the source protected and licensing wired in, package the app into an executable per operating system so customers can run it without installing Python. Build from a clean checkout each release: regenerate the obfuscated artifact, compile the native modules, embed the licensing client, then package — and test the licensed and unlicensed paths on a clean machine before you ship.
Honest limitations
Licensing raises the cost and friction of using your software without paying; it doesn't make piracy impossible. A determined attacker with enough time can still strip a client, and no scheme survives a customer who's willing to stay offline forever and never update. The realistic goal is the same business bar as source protection: make cracking cost more than the license, and keep anything that must stay truly secret — signing keys, master credentials, high-value logic — on infrastructure you control.
Protect the source before you license it
Obfuscate your Python free — rename identifiers and encrypt strings so your licensing code and secrets are not readable in the shipped build.
Open the Python ObfuscatorFree tools mentioned here
Related guides
Frequently asked questions
Why not just check the license key inside my Python code?
Because the check runs on the customer's machine, and they control it. As shown above, an attacker can override the check function at runtime — no valid key needed — and unlock the feature. Obfuscation makes that harder to find but not impossible. The authoritative decision has to happen on a server you control, with a signed response the client can't forge.
Do I need to obfuscate before adding licensing?
Yes — protect the source first. If your licensing code ships as readable Python, an attacker can see exactly where the check is and remove it. Obfuscate (and ideally compile the sensitive modules), then wire in server-validated licensing on top. See the protect-before-selling pipeline for the full order of operations.
How does hardware binding work?
The client computes a stable device fingerprint (from hardware identifiers) and sends it with the key; your server ties the key to that fingerprint on first activation and rejects mismatches after. It stops one license from being shared across many machines. Tools like Licers handle the fingerprinting and server-side binding for you.
What about customers with no internet?
Use signed offline activation: issue a license file cryptographically signed for a specific machine and validity window. The app verifies the signature locally (verifying a signature is safe even on an untrusted client) and re-validates online when it next connects. This supports air-gapped use without making the local check the weak point again.
Is licensing enough to stop piracy completely?
No — nothing is. Licensing raises the cost and friction of unauthorized use; a determined attacker with time can still crack a client. The practical goal is to make cracking cost more than a license, and to keep your highest-value logic or data on a server that only serves validated licenses, so a cracked client gets nothing worth stealing.