Py2Native Python to native compiler

← All posts

2026-08-28

Add License Key Verification to Your Compiled Python App

Add License Key Verification to Your Compiled Python App
python compiler cython code protection open source

Add License Key Verification to Your Compiled Python App

Once you have compiled custom Python into native machine code with Py2Native, the next practical question is usually: how do you control who can run the binary? Compilation hides proprietary source, but it does not stop someone from copying the compiled executable and running it anywhere. License key verification in compiled Python closes that gap. In this guide, you will generate a signing keypair, issue a signed JWT license, call Py2Native Pro’s verification function from your Python code, and build the whole thing with uv run py2native.

Why License Key Verification Matters for Compiled Python Apps

Shipping plain Python means shipping readable source. Even if your clients are honest, the code is right there: function names, business logic, API keys, database queries, pricing rules. Py2Native solves the source-disclosure problem by compiling your custom Python into native machine code. The result is a .pyd, .so, .dylib, or executable instead of a folder full of .py files.

But native compilation alone is not access control. A compiled binary can still be copied to another machine and executed. That is where license key verification comes in. Verification adds a runtime gate: your application checks a signed license before it runs, and exits if the license is missing, expired, or tampered with.

The Community edition of Py2Native focuses on compilation. The Pro plugin adds license verification, including JWT signing, key management, and a compiled verification path. Critically, the elliptic key verification is implemented in compiled code, so the check itself is not a plain Python if statement that can be trivially patched.

What a License Verification Proof Looks Like

Py2Native Pro uses JWT-style signed licenses with EC P-256 keys. A license starts as a JSON payload of claims:

{
  "sub": "customer-123",
  "exp": 1798761600,
  "features": ["pro"]
}

The exp claim is a Unix timestamp that defines when the license expires. You can include any claims that matter for your product: customer ID, feature flags, tier, or build limits.

The Pro plugin signs that payload with your private key. The result is a JWT with three segments: header, payload, and signature. In practice, a license looks like three base64url-encoded segments separated by dots:

eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJjdXN0b21lci0xMjMiLCJleHAiOjE3OTg3NjE2MDB9.<signature>

The signature is computed over the header and payload. Any change to the payload invalidates the signature.

This is very different from a hardcoded check such as:

if license_key != "S3CRET-KEY-123":
    sys.exit("Invalid license")

That approach puts the expected key in the binary, where it can be extracted and reused. A signed JWT does not work that way. The private key is never shipped. The compiled application embeds only the public key and the verification logic, so it can validate the signature locally without contacting a license server.

Step-by-Step: Adding License Verification with Py2Native Pro

These steps assume you have uv, a supported CPython version, a platform C compiler, and the Py2Native Pro plugin available in your project. All commands use uv run py2native.

Prerequisites

Py2Native supports CPython 3.11–3.15, including free-threaded builds, on Windows, Linux, and macOS with x86-64 or ARM64. The open-source core depends on Cython, setuptools, and uv. The Pro plugin is closed-source and adds JWT verification, string compression, and key management commands.

Step 1: Generate a keypair

Generate an EC P-256 keypair:

uv run py2native keygen private.pem public.pem

private.pem is your signing key. Keep it private. public.pem is the verification key that will be embedded in your compiled application.

Step 2: Create a customer license

Sign a JWT payload with your private key:

uv run py2native sign --private private.pem \
  '{"sub":"customer-123","exp":1798761600,"features":["pro"]}' \
  customer-license.dat

Inspect the license to confirm its claims and signature:

uv run py2native show --public public.pem customer-license.dat

This displays the JWT claims and optionally verifies the signature against the public key. Keep this license file separate from your Py2Native Pro build license.

Step 3: Add a .pxd declaration for the verifier

Py2Native Pro provides a compiled verification entry point. To call it from your Python code, add a small .pxd file to your project:

# py2native_license.pxd
cdef extern from "py2native_pro_license.h":
    bint py2native_verify_license(const char* license_key)

This is not a manual Cython workflow. Py2Native reads the declaration during its build step and wires your Python call to the embedded native verifier. If your Pro distribution uses a different symbol, use the declaration shipped with your plugin.

Step 4: Call the verification function from Python

Read the customer license from a file or environment variable, then call the verifier before your application starts:

import sys
from pathlib import Path
from py2native_license import py2native_verify_license

LICENSE_FILE = Path(__file__).with_name("customer-license.dat")

def enforce_license():
    try:
        license_key = LICENSE_FILE.read_text(encoding="utf-8").strip()
    except FileNotFoundError:
        sys.exit("License file not found. Place customer-license.dat next to the executable.")

    if not py2native_verify_license(license_key):
        sys.exit("License verification failed. Contact support for a valid license.")

if __name__ == "__main__":
    enforce_license()
    print("License verified. Starting application...")

The exact import name comes from your .pxd declaration. The important behavior is clear: a missing or invalid license stops execution before any proprietary logic runs.

Step 5: Build the application

Build with the Pro plugin enabled and the public key embedded:

uv run py2native build \
  --license pro-license.dat \
  --public public.pem \
  main.py *.py

Here, --license points to your Py2Native Pro build license, not the customer license you ship to users. --public embeds the public key that the compiled verifier will use at runtime.

The build process bakes the signature verification code and the public key into the executable. You do not need to ship public.pem to customers, and verification does not require network access.

Interpreting Verification Results and Handling Edge Cases

When verification succeeds, your application proceeds normally. The compiled verifier confirms that the license was signed by your private key and has not been modified.

Failures should fail closed. Common failure modes include:

Condition Expected result
Valid signed license Verification succeeds; app runs
Invalid signature Verification fails; app exits
Expired license Verification fails based on exp
Missing license file FileNotFoundError; app exits with a clear message
Tampered license file Signature check fails; app exits

Use a non-zero exit code on failure so shell scripts, schedulers, and CI systems can detect unlicensed execution. A simple sys.exit("message") works, but you can also use sys.exit(1) for integration with external tooling.

Renewal is a re-sign operation. Generate a new license with a later exp claim and send it to the customer. You do not need to rebuild the application or rotate keys unless your private key is compromised.

Offline verification works because the public key and verification logic are embedded in the binary. If you need floating licenses or central revocation, that is outside Py2Native’s built-in feature set. You could layer a server-side check on top, but the cryptographic license gate still works without the network.

Best Practices for License Verification in Compiled Python

  • Never distribute private.pem. It is the root of trust for your licenses. Store it in a secrets manager or offline signing machine.
  • Use short expiration periods. Short-lived licenses give you a natural renewal conversation and limit the cost of a leaked license.
  • Keep the Pro build license separate from customer licenses. The --license flag on build is for the Pro plugin. Customer licenses are verified by your compiled application.
  • Test on every target platform. Py2Native supports Windows, Linux, and macOS with x86-64 and ARM64. Run the verification flow on each platform you ship to.
  • Use a standard license location or environment variable. A predictable path next to the executable, or an environment variable such as MYAPP_LICENSE, simplifies deployment and support.
  • Combine verification with native compilation. Py2Native already compiles your custom Python into native machine code, hiding source. License verification adds the access-control layer on top.

FAQ

Can I use Py2Native’s license verification without the Pro plugin?

No. License verification is a feature of the Pro plugin. The open-source Community edition focuses on compilation only. The Pro plugin adds JWT signing, verification, and key management commands.

Does the compiled binary need to contact a license server?

No. Py2Native’s license verification is entirely offline. The public key and verification logic are embedded in the binary, so the application can validate the JWT signature locally without any network access.

How do I handle license expiration or feature limits?

You can include claims in the JWT payload, such as an expiration timestamp or allowed features. Your Python code can read these claims after verification and enforce the appropriate restrictions.

What happens if someone modifies the license file?

The JWT signature will fail verification because the signature is computed over the payload. Any tampering invalidates the signature, and the verification function will return an error, preventing the application from running.

Conclusion

Compiling Python to native code protects your source. Adding signed JWT license verification protects your revenue and distribution. Py2Native Pro makes the workflow practical: generate a keypair, sign a license, call the compiled verifier, and build with uv run py2native. The result is a native binary that checks a cryptographic license before running—without a license server, without third-party JWT libraries at runtime, and without shipping your private key.

If you are already using the Community edition, the Pro plugin is the focused upgrade for license enforcement. To get started, see the Py2Native documentation.

Related posts

EU label: AI-generated content