How to Verify JWT Licenses in Python Compiled Binaries with Py2Native Pro
How to Verify JWT Licenses in Python Compiled Binaries with Py2Native Pro
Verifying a JWT license inside a Python application that has been compiled to native machine code is one of the most reliable ways to protect proprietary software from casual piracy and tampering. But doing it without exposing the verification logic—or the public key—requires more than just a JWT library. Py2Native Pro bakes the verification code and the public key directly into your compiled binary, so license checks happen in native code that is significantly harder to inspect or modify than Python bytecode.
In this guide, you’ll learn exactly how to generate an EC P-256 keypair, sign a JWT license, declare the verification function with a .pxd file, and build your application with Py2Native Pro. We’ll also cover how to interpret verification results and handle edge cases.
Why JWT License Verification Matters for Compiled Python
Shipping plain .py files means anyone with access to the source can read, copy, or modify your code. Even if you compile your code with something like Cython, the license verification logic often remains in Python and can be patched, bypassed, or reverse-engineered relatively easily.
A JWT (JSON Web Token) license is a signed, tamper-evident document. The token contains a header, a payload with claims, and a signature. If an attacker changes even one character, the signature no longer matches and verification fails. This makes JWT a strong format for license enforcement—provided the verification code itself is not easy to modify.
The challenge: if the public key or the verification function stays in Python source or bytecode, an attacker might swap in a different public key or neuter the check. Py2Native Pro solves this by compiling your custom Python code into native machine code and embedding both the verification logic and the public key inside the executable. The private key never leaves your build machine.
What a JWT License Proof Looks Like
A JWT license generated by Py2Native Pro is a standard JWT with three Base64URL-encoded parts separated by dots:
- Header – typically
{"alg":"ES256","typ":"JWT"}. - Payload – contains vendor-defined claims, for example:
exp– expiration timestampsub– customer identifier or product namefeatures– allowed feature flags
- Signature – computed over the first two parts using your EC P-256 private key.
The signing process is handled by the Pro plugin:
uv run py2native keygen private.pem public.pem
This generates an EC P-256 keypair. Keep private.pem secure on your build machine; you’ll use it to sign licenses. The public.pem file is later embedded in the compiled binary for verification.
To sign a license, create a JSON payload and run:
uv run py2native sign --private private.pem payload.json license.dat
The resulting license.dat file contains the signed JWT. At build time, you pass both the license and the public key to Py2Native:
uv run py2native build --license license.dat --public public.pem main.py
The Pro plugin bakes the elliptic-key verification code and the public key into the executable. Note that only the public key is stored in the binary; the private key is never distributed. Moreover, the elliptic verification is implemented in compiled code, avoiding third-party libraries that could be patched or replaced.
Step-by-Step: Verifying a JWT License with Py2Native Pro
Here’s the complete workflow from key generation to runtime license check.
Prerequisites
- A project using CPython 3.11–3.15 (including free-threaded variants).
- A platform C compiler (MSVC, GCC, or Clang).
uvinstalled, version 0.11.8 or later.- Py2Native Pro plugin (
py2nativepro) available in your environment.
Step 1: Generate an EC P-256 keypair
uv run py2native keygen private.pem public.pem
This creates private.pem (vendor-only) and public.pem (embedded in the binary). Store the private key in an offline, access-controlled location.
Step 2: Sign a JWT payload
Create a payload file, for example payload.json:
{
"sub": "customer-123",
"exp": 1787222400,
"features": ["pro", "reports"]
}
Sign it:
uv run py2native sign --private private.pem payload.json license.dat
You can inspect the resulting token without exposing the private key:
uv run py2native show --public public.pem license.dat
This displays the JWT claims and verifies the signature against the public key.
Step 3: Declare the verification function with a .pxd file
For normal code compilation, Py2Native accepts plain Python with no Cython syntax. For Pro license verification, you include a single .pxd declaration file that tells the compiler how to call the built-in verification routine. The Pro plugin provides the native function; your .pxd file just declares it.
Create a file named license_verify.pxd with a declaration similar to:
cdef extern from "py2native_pro.h":
int py2native_verify_license(const char* license_path, const char* public_key_path)
The exact function name and signature are supplied by the Pro plugin; consult its generated header after installation. This .pxd file is not a Cython module you compile manually—it only declares the external symbol for the build orchestrator.
Step 4: Call the verification function from your Python code
In your main.py (or any module that runs at startup), import the declared function and call it:
from license_verify cimport py2native_verify_license
def check_license():
# license.dat and public.pem are embedded or expected at a known path
result = py2native_verify_license(b"license.dat", b"public.pem")
if result != 1:
print("License invalid or expired.")
exit(1)
return True
The Pro plugin handles linking the native verification function into your executable, so you don’t write any C or Cython logic yourself.
Step 5: Build the application
Run the build command, passing the license and public key:
uv run py2native build --license license.dat --public public.pem main.py
The Pro plugin verifies that you have a valid license file, then bakes the verification code and public key into the final native executable (or shared library, if you also use --library). At runtime, your code calls the compiled verification function.
Interpreting Verification Results and Handling Edge Cases
The compiled verification function returns a boolean-like integer (usually 1 for valid, 0 for invalid). Here’s what to expect:
- Valid license – the function returns
1; your application continues normally. - Expired license – the
expclaim is in the past; verification fails and returns0. You should show a clear error and exit, or start in a restricted mode. - Tampered license – any modification to the header or payload invalidates the signature; verification fails.
- Missing license file – if the file is absent, the call returns
0; your code should handle this gracefully. - Wrong public key – if the binary was built with a different key than the one used to sign the license, verification always fails.
Clock skew can be an issue if the customer’s system clock is ahead of the expiration time. To mitigate, consider issuing licenses with a short grace period or using a relative expiration based on the first run time.
For products that have multiple editions or feature sets, include a features claim in the JWT payload and gate functionality accordingly in your Python code. The verification function only checks the signature and expiration; it’s up to your application to enforce feature-level permissions.
Finally, remember that third-party Python libraries are left unmodified and may still be monkey-patched. Py2Native protects your custom code, including the license verification logic, but it does not prevent an attacker from altering libraries that remain as Python source. Keep your core verification checks in compiled code.
Best Practices for JWT License Verification in Compiled Binaries
- Keep the private key offline. Generate keys on a secure machine and never commit
private.pemto version control. Use short expiration times (e.g., 30–90 days) and implement a renewal mechanism for paying customers. - Combine with string compression. Py2Native Pro includes string compression, which further obscures sensitive strings (like error messages or feature names) in the compiled binary.
- Test on all target platforms. Py2Native Pro works on Windows, Linux, and macOS. Build and run your license verification on each platform to ensure the native linking and JWT checks work consistently.
- Use the
showcommand during development. Before shipping, inspect signed tokens withuv run py2native show --public public.pem license.datto verify claims and signatures. - Handle failure gracefully. Instead of crashing with a traceback, show a user-friendly message and exit or enter a limited mode. This reduces support friction and avoids leaking internal details.
FAQ: JWT License Verification with Py2Native Pro
Q: Can I verify a JWT license in a Python compiled binary without Py2Native Pro?
A: Yes, you could manually implement JWT verification using a library like PyJWT and then compile your code with Cython or another tool. However, that requires writing Cython code, managing .pyx files, and ensuring the verification logic is not exposed. Py2Native Pro automates this by baking the verification code and public key into the executable, with no manual Cython steps.
Q: How does Py2Native Pro protect the JWT verification logic from reverse engineering?
A: The verification logic is compiled into native machine code, making it much harder to inspect or modify than Python bytecode. Additionally, the public key is embedded in the binary, and the elliptic key verification is handled with compiled code, avoiding third-party libraries that could be patched.
Q: What happens if the JWT license is expired or invalid?
A: Your Python code can call the verification function and receive a boolean result. You decide how to handle failure: show an error message, exit the application, or run in a limited mode. The compiled binary will not contain the private key, so an attacker cannot forge a valid license.
Q: Does Py2Native Pro support license verification on all platforms?
A: Yes, Py2Native Pro works on Windows, Linux, and macOS, and the verification code is compiled for the target platform. The same JWT license file can be verified across platforms as long as the public key matches.
Conclusion
Verifying JWT licenses inside compiled Python binaries is a powerful way to protect your revenue and intellectual property. Py2Native Pro removes the complexity of manual Cython configuration and native linking, giving you a zero-config path from plain Python to a protected executable with embedded JWT verification.
To get started, generate a keypair, sign a license, add the .pxd declaration and a single call to your code, then build with --license and --public. The result is a native binary that checks license validity without exposing the private key or the verification logic.
Ready to compile and protect your Python code? Explore Py2Native and start building secure, license-enforced binaries today.