Secure Your Python App with Built-in License Verification
You’ve built a Python application that solves a real problem. The next decision is how to ship it without handing over readable source code and a license check that can be deleted in 30 seconds. That’s where python code licensing verification becomes critical.
Py2Native approaches this differently from a plain Python script. Instead of leaving your license check as an editable .py file, Py2Native compiles your custom Python into native machine code. The Pro plugin can then inject the verification logic and public key directly into the compiled binary.
You could hand-write C extensions or maintain a raw Cython build to protect a check. That’s the hard way. Py2Native hides that toolchain and gives you a command-line workflow that is actually practical to ship.
Why License Verification Matters for Python Developers
Commercial Python software has a fundamental tension: Python source is readable, while your revenue depends on enforcing license terms.
Typical Python distribution ships .py files. Even if you add a license check in plain Python, a user can open the file, find the if license_is_valid branch, and patch it. The barrier to modification is very low.
Native compilation raises that barrier. When your custom code becomes native machine code, there is no source file to edit. But native compilation alone is not enforcement. You still need a license check that lives inside the binary and cannot be bypassed by editing a script.
Py2Native’s approach is to compile your Python to native code and embed license verification logic in the same build. The open-source core handles compilation. The Pro plugin adds key generation, license signing, inspection, and native verification hooks.
What a License Verification Proof Looks Like
A useful license system has three parts:
- A signed license file with claims such as customer ID, expiry, and allowed features.
- A public key baked into the binary.
- Verification code that checks the license signature and claims at runtime.
Py2Native Pro uses asymmetric elliptic curve cryptography. You generate an EC P-256 keypair. The private key signs licenses. The public key verifies them.
Only the public key is stored in the executable. The private key never ships with your app.
A license payload can be a JSON object:
{
"sub": "customer@example.com",
"exp": 1798761600,
"features": ["reports", "api"]
}
sub identifies the customer, exp is the expiration timestamp, and features can gate specific parts of your application.
The signed result is a JWT. A modified license file will no longer match the signature, so verification fails. The elliptic key verification is handled with compiled code inside Py2Native Pro, not by a third-party library in plain Python.
Step-by-Step: Adding Verification with Py2Native Pro
This workflow uses uv run py2native for every command.
Step 1: Generate an EC P-256 keypair
Generate the private and public keys:
uv run py2native keygen private.pem public.pem
Keep private.pem secure. You will use it only to sign licenses. public.pem is the key that gets embedded in your compiled app.
Step 2: Sign a customer license JWT
Create a signed license for a customer:
uv run py2native sign \
--private private.pem \
'{"sub":"customer@example.com","exp":1798761600,"features":["reports","api"]}' \
customer-license.dat
This produces customer-license.dat. That file goes to the customer. It is not stored inside your compiled binary.
The --license flag you use later during a Py2Native Pro build is a separate Py2Native Pro build license. Do not confuse the customer license file with your own Pro build license.
Step 3: Declare the native verification entry point in a .pxd file
Py2Native Pro uses the .pxd file to wire your Python code to the injected native verification logic. You are not writing a raw Cython build and not running cythonize yourself.
A minimal .pxd declaration shows the shape, but you should use the exact declaration provided with your Pro plugin version:
# py2nativepro_verify.pxd
# Declares the Pro plugin's compiled license-check entry point.
# The exact function name and signature may vary by Pro plugin release.
cdef extern from "py2nativepro_license.h":
int py2nativepro_verify_license(const char* license_path, const char* public_key_path)
This file tells Py2Native what native verification function to expose to your Python module during compilation.
Step 4: Call the verification function from your Python code
Inside your application, call the verification function and fail closed when the result is not valid.
The generated module name and return type come from the .pxd declaration. The example below uses a simple integer status where 0 means valid:
# main.py
import sys
# This module is generated by Py2Native Pro from your .pxd declaration.
from license_verifier import verify_license
VALID = 0
def main():
license_path = "customer-license.dat"
status = verify_license(license_path)
if status != VALID:
# Fail closed. Do not continue into the protected application logic.
print(f"License verification failed with status {status}", file=sys.stderr)
sys.exit(1)
print("License valid. Starting application...")
if __name__ == "__main__":
main()
The plugin bakes the verification code and public key into the executable. Your Python call is compiled with everything else.
Step 5: Build with the public key embedded
Build your app with Py2Native Pro:
uv run py2native build \
--public public.pem \
--license pro-license.dat \
main.py
Here, --public public.pem tells Py2Native which public key to embed for license verification. --license pro-license.dat is your Py2Native Pro build license that enables Pro features.
If you want a deployment directory or a wheel, add --embed or --wheel:
uv run py2native build \
--public public.pem \
--license pro-license.dat \
--embed dist/linux \
main.py
The customer license file is not baked in. The app reads customer-license.dat from the customer’s environment and verifies it against the embedded public key.
Interpreting Verification Results and Handling Edge Cases
Your verification call should fail closed. Treat anything other than a valid result as a reason to stop loading protected features.
Common edge cases to handle:
- Expired license: The
expclaim is in the past. Verification should return an expired state. Ask the customer to renew. - Tampered license: Any modification to the JWT breaks the signature. Verification fails.
- Missing public key during development: If you run the
showcommand or inspect a license outside a compiled app, pass the public key explicitly. - Clock skew: The customer machine’s clock affects
expchecks. Decide whether to use an operational grace period on your side.
When debugging a license, inspect its claims without running the full app:
uv run py2native show --public public.pem customer-license.dat
This is useful before sending a license to a customer. Use it to confirm the subject, expiry, and feature claims match what you intended to issue.
Because the Pro plugin compiles the verification routine into native code, the check is not a plain Python function that can be monkey-patched away from a .py file. That is the practical security difference from a source-distributed check.
Best Practices for Robust License Enforcement
- Protect the private key. Keep it offline or in a secrets manager. Never ship it with the app or store it in source control.
- Use short expiration times. Issue licenses with a clear expiry and provide a renewal path.
- Gate features with claims. Use the
featuresclaim to enable or disable parts of your application instead of maintaining separate license types. - Combine verification with native compilation. The native binary protects the check from casual editing. For more on the compilation side, see Automate Python Compilation with uv and Py2Native.
- Test on every target platform. Py2Native supports Windows, manylinux/musl Linux, and macOS. Run your verification path on each platform before shipping.
- Use
--embedor--wheelfor distribution. Those modes package the compiled app with the license check included, while keeping your Python workflow simple.
FAQ
Can I use Py2Native’s license verification without the Pro plugin?
No. Key generation, signing, inspection, and the embedded verification code are part of the Pro plugin. The open-source core focuses on compilation only.
How does the verification code get into my compiled binary?
When you build with the Pro plugin and include a .pxd file, the plugin injects the verification logic and the public key into the generated native code. Your Python call is compiled along with everything else.
What happens if a user modifies the license file?
The JWT signature will no longer match the embedded public key, so verification fails. Py2Native Pro uses EC P-256 cryptography to ensure tampering is detected.
Can I verify licenses in a Python script that isn’t compiled?
The verification function is designed to be compiled into a native binary. Running it in a plain Python script would require the Pro plugin runtime, which is not intended for uncompiled use. For maximum security, compile your app with Py2Native.
Conclusion
License verification only works when the check is as difficult to remove as the application logic it protects. Py2Native compiles your custom Python into native machine code, and the Pro plugin puts signature verification and the public key inside that binary.
Start with a keypair, sign a JWT, call the verification function from your code, and build with --public. That is a practical path from readable Python source to a protected, license-aware native application.
If you’re shipping proprietary Python software, Py2Native gives you the compilation and verification tooling in one uv run py2native workflow.
Related posts
- The Best Python Native Compiler for Proprietary Software
- How to Protect Python Source Code: A Beginner’s Guide
- Automate Python Compilation with uv and Py2Native