Offline RSA-signed license management — CLI, GUI, and Python API.
Project description
py-Rizmi Licensing
Offline software licensing toolkit using RSA-signed JWT tokens.
Generate license keys, validate them against machine fingerprints (HWID),
and manage the full lifecycle — via PyQt6 GUI, CLI, or Python API.
Table of Contents
- Overview
- Features
- Architecture
- Quick Start
- GUI Usage Guide
- CLI Reference
- Integration Workflow
- Testing
- Building an Executable
- Project Structure
- Contributing
- License
Overview
py-Rizmi Licensing enables you to issue, validate, and inspect offline RSA-signed license files using JWT tokens. It is built for deployment environments where internet access is unavailable — the license is validated locally using a public key and a hardware fingerprint (HWID).
The entire core layer is pure Python with zero GUI dependencies, making it suitable for integration into any Python application or web backend.
Features
- RSA Keypair Management — Generate (2048/3072/4096-bit), load, paste, and validate RSA keypairs. Verify that private and public keys match.
- Machine Fingerprinting — Deterministic SHA-256 hardware ID based on
the OS-level machine identifier. Pluggable via
FingerprintProviderProtocol. - License Issuance — Sign arbitrary payload fields into a JWT token
and save as a
.licfile withschema_versionfor future-proofing. - License Validation — Verify signature, expiration (with grace period enforcement), and HWID match on any machine.
- License Viewer — Decode and inspect any
.licfile with the matching public key — no private key needed. - Integration Guide — In-app rendered README with Python API docs and backend integration examples.
- Public API — Curated
__all__surface (LicenseValidator,LicenseIssuer,KeyPair,MachineFingerprint,LicensePayload) covered by SemVer. - PyQt6 GUI — Sidebar-navigated desktop application for cross-platform use.
- CLI — Headless issuance, key generation, validation, and HWID retrieval
via
rizmicommands (Typer + Rich). - Backend Module — Drop-in validation function for app-server integration.
- Fully Tested — 60+ pytest tests with Hypothesis property tests, contract tests, regression tests, e2e tests, GUI tests, and ruff + mypy enforcement.
Architecture
py_rizmi/core/ ← Pure Python. No GUI. 100% testable.
py_rizmi/models/ ← LicensePayload dataclass with schema_version.
py_rizmi/integrations/ ← Server-side validation helper.
py_rizmi/gui/ ← PyQt6 widgets. Depends on core/. Optional [gui] extra.
py_rizmi/cli/ ← Typer CLI (rizmi command). Depends on core/.
py_rizmi/_internal/ ← Private implementation — never import directly.
tests/ ← pytest suite. Imports only core/.
Fingerprint sources implement the FingerprintProvider Protocol, making
the fingerprinting layer extensible without modifying core code.
Every payload field is a bound input widget — there is zero hard-coded payload data anywhere in the codebase.
Quick Start
# Recommended — with uv (uses the lockfile for reproducible installs)
uv sync --extra dev # development (core + test tools)
uv sync --extra gui # with GUI support
uv sync --extra all # everything
# With pip (manually resolve dependencies)
pip install py-rizmi # core only
pip install py-rizmi[gui] # with GUI
pip install -e ".[dev]" # local development
You have two ways to use the toolkit:
🖥️ GUI Mode
Launch the full desktop application with sidebar navigation:
# Recommended (installed package)
rizmi gui
# Alternative (run directly from repo)
python main.py
Requires the
[gui]extra. If not installed,rizmi guiwill print a friendly install hint instead of crashing. Install with:pip install py-rizmi[gui] # or with uv: uv sync --extra gui
All features — key generation, license issuance, viewer, and the integration guide — are accessible through the interface.
⌨️ CLI Mode
The rizmi command (Typer + Rich) provides headless access to all features:
# Generate an RSA keypair
rizmi keys generate \
--private-out keys/private_key.pem \
--public-out keys/public_key.pem \
--key-size 2048 \
--passphrase
# Get this machine's hardware fingerprint
rizmi machine-id
# Issue a signed license file
rizmi license issue \
--private-key keys/private_key.pem \
--output license.lic \
--client "Acme Corp" \
--license-id "deploy-001" \
--hwid "<paste-the-hwid-here>" \
--features billing --features reports \
--max-clients 10 \
--grace-days 14 \
--exp-days 365
# Validate and inspect a license
rizmi license validate license.lic --public-key keys/public_key.pem
rizmi license inspect license.lic --public-key keys/public_key.pem
GUI Usage Guide
The application opens a window with a sidebar on the left and a content area on the right. Click any navigation item to switch views.
Machine ID
Navigate to Machine ID in the sidebar, then:
- Click Generate Machine ID.
- The raw fingerprint and SHA-256 hash are displayed.
- Click Copy HWID and send this hash to your license issuer.
Key Management
Navigate to Key Management in the sidebar. Generate, load, and validate RSA keypairs.
- ① Generate Keypair — Select key size (2048, 3072, or 4096) and click Generate. The private and public PEM are displayed in read-only text areas. Use Save and Copy to export or copy each key.
- ② Load Keys — Browse for existing
.pemfiles on disk, or Paste PEM content from the clipboard. - ③ Validate Pair — Click Validate Keypair to confirm both PEMs belong together. Result shows key size or a mismatch error.
License Generation
Navigate to License Generation in the sidebar.
- ① Signing Key — Browse for an existing private key
.pemfile. - ② License Payload — Fill in every field:
- Client / Deployment (required)
- License ID (required)
- HWID (required — click ← Tab 1 to pull from Machine ID view)
- Features (add/remove dynamically)
- Max Clients, Mode, Server URL, Grace Days
- Issued At (iat) and Expiration (exp) — auto or manual.
- Click Preview Payload (JSON) to inspect the data before signing.
- Click Generate License and save the
.licfile.
License Viewer
Navigate to License Viewer in the sidebar.
- Select the matching public key
.pemfile. - Select the license file
.licto inspect. - Click Decode & View — all fields are displayed read-only, along with expiry status and days remaining.
Integration Guide
In-app rendered view of this README — Python API docs, CLI usage, and backend integration instructions.
CLI Reference
The rizmi CLI is the recommended interface for all headless operations.
Every command supports --help / -h:
rizmi --help
rizmi keys --help
rizmi license --help
rizmi machine-id --help
rizmi gui --help
Key Management — rizmi keys
| Command | Description |
|---|---|
rizmi keys generate |
Generate a new RSA keypair (2048 / 3072 / 4096 bits) |
rizmi keys inspect <key.pem> |
Show key type, size, and fingerprint |
rizmi keys verify --private ... --public ... |
Verify a private/public key pair matches |
License Operations — rizmi license
| Command | Description |
|---|---|
rizmi license issue |
Sign and write a .lic token file |
rizmi license validate <file.lic> |
Validate signature, expiry, and HWID |
rizmi license inspect <file.lic> |
Decode and display all payload fields |
Machine Fingerprint — rizmi machine-id
rizmi machine-id # rich panel output
rizmi machine-id --raw # plain hash only (for piping)
rizmi machine-id --copy # copy to clipboard
GUI — rizmi gui
rizmi gui # launch the PyQt6 desktop application
Requires the [gui] extra (pip install py-rizmi[gui]). Without it,
the command exits with code 1 and prints a clear install hint.
Full Example Workflow
# 1. Generate keys
rizmi keys generate --private-out keys/private.pem --public-out keys/public.pem
# 2. Get HWID of the target machine
rizmi machine-id --raw
# 3. Issue a license
rizmi license issue \
--private-key keys/private.pem \
--output license.lic \
--client "Acme Corp" \
--license-id "deploy-001" \
--hwid "<hwid-from-step-2>" \
--features billing --features reports \
--exp-days 365
# 4. Validate on the target machine
rizmi license validate license.lic --public-key keys/public.pem
# 5. Inspect a token (author side, no HWID check)
rizmi license inspect license.lic --public-key keys/public.pem
To protect the private key at rest, add
--passphrasetokeys generate. When issuing with an encrypted key, pass--key-passphraseor set theRIZMI_KEY_PASSPHRASEenvironment variable.
Integration Workflow — From Start to Finish
The recommended path is to use rizmi gui (or python main.py) for interactive
tasks and fall back to CLI commands (rizmi ...) when you need
to automate or work on a headless server.
Step 1 — Generate an RSA Keypair
GUI (recommended): Open the app → Key Management view → pick a key
size → click Generate → Save both .pem files.
CLI (headless/automation):
rizmi keys generate \
--private-out keys/private_key.pem \
--public-out keys/public_key.pem \
--key-size 2048
🔒
private_key.pemstays on your authoring machine — never ship it.
Step 2 — Get the Machine HWID
Run this on the target machine where the licensed app will be deployed.
GUI (recommended): Open the app (rizmi gui) → Machine ID view → click
Generate Machine ID → Copy HWID.
CLI (headless server):
rizmi machine-id
# HWID (SHA-256): fb50b7767d233a9ecc952dd9c11760586b3bd1a40d6bfbec051a312f0b51c77c
Send this hash to your license author.
Step 3 — Issue a License
GUI (recommended): Open the app → License Generation view → select the private key, fill in the payload fields (including the HWID from Step 2), add features, set dates → Generate License.
CLI (headless/automation):
rizmi license issue \
--private-key keys/private_key.pem \
--output license.lic \
--client "Acme Corp" \
--license-id "deploy-001" \
--hwid "<paste-hwid-here>" \
--features billing --features reports \
--max-clients 10 \
--exp-days 365
Deliver public_key.pem and license.lic to the developer integrating
the app.
Step 4 — Integrate Validation Into Your App
The developer embeds public_key.pem and license.lic and validates
at startup — no GUI or script needed here, just the Python API:
from py_rizmi import LicenseValidator
validator = LicenseValidator.from_file("path/to/public_key.pem")
try:
payload = validator.validate_from_file("path/to/license.lic")
print(f"Licensed to {payload.client}")
if payload.in_grace_period:
print("Warning: license is in grace period")
except ValueError as e:
print(f"License invalid: {e}")
Step 5 — Server-Side Drop-In (Optional)
For apps with a validation server:
from py_rizmi.integrations.validation import validate_license
try:
payload = validate_license("/path/to/config/dir")
# config dir must contain public_key.pem and license.lic
print(payload["client"], payload["features"])
except ValueError as e:
print(f"License invalid: {e}")
Testing
# Fast unit tests (no GUI dependencies)
uv run pytest -p no:qt tests/unit -v
# Full test suite (requires PyQt6 + system libEGL)
uv run pytest -v
# Linting & type checking
uv run ruff check .
uv run mypy src
# With pip (no lockfile)
pip install -e ".[dev,gui]"
pytest -v
All core tests cover the public API without any GUI dependencies.
Building an Executable
This project uses Nuitka to compile the Python code into a standalone native executable for Linux or Windows.
Prerequisites
# Nuitka is a dev dependency
uv sync --extra dev
# Linux: gcc / g++ must be installed
sudo apt install gcc g++ python3-dev # Debian / Ubuntu
# Windows: Download and install MSVC from Visual Studio Build Tools
Build
# Standalone folder (recommended — faster build, easier debugging)
bash build.sh standalone
# Single executable (longer build, larger file)
bash build.sh onefile
Output goes to dist/py-rizmi/.
Cross-platform note: Build on each target OS separately. Linux builds produce Linux binaries, Windows builds produce
.exe. Use GitHub Actions with matrix runners (ubuntu, windows) to automate this.
What Gets Bundled
| Resource | How | Why |
|---|---|---|
media/logo.png |
--include-data-dir |
Window icon & in-app logo |
README.md |
--include-data-file |
Integration Guide view |
| PyQt6, qdarktheme, markdown, PyJWT, cryptography | Auto-detected by Nuitka | Runtime dependencies |
Project Structure
py-rizmi/
├── main.py # GUI entry point
├── pyproject.toml # Hatchling + hatch-vcs build config
├── build.sh # Nuitka build script
├── CHANGELOG.md # Keep-a-Changelog
├── CONTRIBUTING.md # Development guide
├── docs/
│ ├── api-stability.md # SemVer policy
│ └── adr/
│ └── 0001-pyqt6-licensing.md # Architecture Decision Record
├── .github/workflows/
│ ├── ci.yml # CI: lint + fast tests + full tests
│ └── release.yml # Release: build → TestPyPI → PyPI → GitHub Release
├── media/
│ └── logo.png # Application logo
├── src/
│ └── py_rizmi/
│ ├── __init__.py # Public API (__all__)
│ ├── _version.py # hatch-vcs generated (gitignored)
│ ├── core/ # Pure logic — no GUI
│ │ ├── crypto.py # RSA primitives
│ │ ├── hwid.py # Machine fingerprint + FingerprintProvider Protocol
│ │ ├── keypair.py # RSA keypair management
│ │ ├── license_issuer.py # Token signing
│ │ └── license_validator.py # Token validation + decode
│ ├── models/
│ │ └── license_payload.py # LicensePayload dataclass with schema_version
│ ├── gui/ # PyQt6 GUI (optional [gui] extra)
│ │ ├── app.py # Main window + sidebar
│ │ ├── theme.py # Styling & theming
│ │ ├── views/
│ │ │ ├── hwid_view.py
│ │ │ ├── keymanager_view.py
│ │ │ ├── generate_view.py
│ │ │ ├── viewer_view.py
│ │ │ └── guide_view.py
│ │ └── widgets/
│ │ ├── step_card.py
│ │ └── dynamic_list.py
│ ├── integrations/
│ │ └── validation.py # Server-side validation helper
│ ├── cli/ # rizmi CLI (Typer + Rich)
│ │ ├── app.py # Root app + help banner + --version
│ │ └── commands/
│ │ ├── keys.py # keys generate / inspect / verify
│ │ ├── license_cmd.py # license issue / validate / inspect
│ │ └── machine_id.py # machine-id (--raw, --copy)
│ └── _internal/ # Private — never import directly
│ └── logging.py
├── keys/ # Generated keys (gitignored)
│ └── .gitkeep
└── tests/ # comprehensive pytest suite
├── unit/
│ ├── core/ # Core cryptography unit tests
│ └── models/ # Data model unit tests
├── integration/ # Hypothesis property tests
├── contract/ # Golden fixtures and compatibility checks
├── regression/ # Specific bug repro/regression tests
├── e2e/ # CLI execution smoke tests
└── gui/ # PyQt6 widget and workflow tests
Contributing
See CONTRIBUTING.md for the full development guide — setup, project layout, code style (ruff + mypy), testing, and the deprecation-shim pattern for public API changes.
Reporting Issues
- Use the GitHub issue tracker.
- Include the full error output and steps to reproduce.
- Mention your Python version and operating system.
Ideas for Contributions
Planned post-1.0 features include key rotation, online validation, certificate revocation lists, and tamper-evident audit logs.
License
This project is provided under the MIT License. See the LICENSE file for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_rizmi-1.3.3.tar.gz.
File metadata
- Download URL: py_rizmi-1.3.3.tar.gz
- Upload date:
- Size: 1.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51ff89ba5dc8ad837bc07b834ebec7255e63f4393bbc2b337aaa80fa1f7ccfbd
|
|
| MD5 |
b475a682e56be609e94d0f1258180ed5
|
|
| BLAKE2b-256 |
6a4002d6c3c8d5bb4cd43565ef0a3091912f79a9d52b9114942881679da7570e
|
Provenance
The following attestation bundles were made for py_rizmi-1.3.3.tar.gz:
Publisher:
release.yml on Ramzi-Hadrouk/py-rizmi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
py_rizmi-1.3.3.tar.gz -
Subject digest:
51ff89ba5dc8ad837bc07b834ebec7255e63f4393bbc2b337aaa80fa1f7ccfbd - Sigstore transparency entry: 2194870055
- Sigstore integration time:
-
Permalink:
Ramzi-Hadrouk/py-rizmi@f09ec66935bd7629672bcd6064e31a18fd00423e -
Branch / Tag:
refs/tags/v1.3.3 - Owner: https://github.com/Ramzi-Hadrouk
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f09ec66935bd7629672bcd6064e31a18fd00423e -
Trigger Event:
push
-
Statement type:
File details
Details for the file py_rizmi-1.3.3-py3-none-any.whl.
File metadata
- Download URL: py_rizmi-1.3.3-py3-none-any.whl
- Upload date:
- Size: 55.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
afe9175b1b7da751f4989cfd898ef1d74ba5fd5918817d21d929c09649b37b1e
|
|
| MD5 |
378109d1c59e67953b2aaadcd4ff30b2
|
|
| BLAKE2b-256 |
fc66ae63526c1321c3aff03e01ec8f31040bbf7e79af87c4bc1db26c6ed13a43
|
Provenance
The following attestation bundles were made for py_rizmi-1.3.3-py3-none-any.whl:
Publisher:
release.yml on Ramzi-Hadrouk/py-rizmi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
py_rizmi-1.3.3-py3-none-any.whl -
Subject digest:
afe9175b1b7da751f4989cfd898ef1d74ba5fd5918817d21d929c09649b37b1e - Sigstore transparency entry: 2194870075
- Sigstore integration time:
-
Permalink:
Ramzi-Hadrouk/py-rizmi@f09ec66935bd7629672bcd6064e31a18fd00423e -
Branch / Tag:
refs/tags/v1.3.3 - Owner: https://github.com/Ramzi-Hadrouk
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f09ec66935bd7629672bcd6064e31a18fd00423e -
Trigger Event:
push
-
Statement type: