Keyvory is a self-hosted license management server. Generate license keys, lock licenses to hardware, manage expirations, track devices, and control feature access through a modern dashboard and secure REST API.
Run it on your own infrastructure with no third-party services, no per-seat pricing, and full control over your data.
git clone https://github.com/Fadi002/Keyvory.git
cd keyvory
pip install -r requirements.txt
python run.pyThe first-run setup wizard generates cryptographic keys, creates an admin account, and seeds default license types. After setup, your server is running at http://localhost:5000.
- License lifecycle — Create, edit, suspend, ban, extend, or delete licenses from the dashboard or API. Each license has a unique key, status, expiration date, and optional notes.
- Hardware locking (HWID) — Licenses bind to SHA-256 hardware fingerprints. The server enforces device limits and rejects logins from unauthorized hardware. Admins can reset or remove individual device bindings.
- License types and templates — Define reusable tiers with configurable duration, device limits, grace periods, and feature flags. Types can be lifetime or time-limited.
- Application scoping — Licenses are scoped to a specific application. Deactivating an application suspends all its licenses.
- License groups — Group licenses for bulk suspend, activate, or extend operations. Useful for organization-level deployments or promotional batches.
- Bulk operations — Create up to 500 licenses at once with full transaction rollback on failure
- IP and device blacklisting — Blacklist IP addresses or device fingerprints with optional expiration dates.
- Two-layer sealed envelope API — Requests/responses are encrypted with AES-256-GCM, with the AES key derived from X25519 key exchange (ephemeral keypairs + HKDF). HMAC-SHA256 integrity check over ciphertext. Ed25519 signatures for response authenticity. Timestamp-based replay protection with nonce caching.
- Rate limiting and IP blocking — Per-endpoint rate limits (20–120 req/min for API, 60/min GET + 20/min POST for login). Automatic IP blocking after 10 failures within 5 minutes, with a 15-minute block duration.
- Account lockout — Dashboard login locks after 5 failed attempts. Auto-resets after 30 minutes. SecurityAlert generated on lockout.
- Security event logging — Every API call, auth attempt, and license operation is logged with severity, category, risk score, source IP, geo-location, and device info.
- Automated threat detection — The server detects brute force attacks, credential stuffing, impossible travel, rapid license activations, API abuse, and repeated invalid license attempts. Security alerts are created with deduplication.
- Threat intelligence dashboard — Heatmap visualization of attacks over time, timeline charts, top threat actors with risk scores, and attack vector analysis.
- Single-file Python SDK — Drop
keyvory_client.pyinto your project. Three dependencies:pycryptodome,requests,pynacl. - Anti-tamper protection — Module integrity hashing (detects runtime patching), debugger detection via
sys.gettrace(), timing-based anomaly detection (5ms threshold), HMAC state chain for in-memory integrity. - Automatic session key rotation — The AES session key rotates every 100 heartbeats via the server.
- Retry with exponential backoff — Configurable retry count and timeout for network failures.
- Dark and light themes — System preference detection with manual toggle. Persisted in local storage.
- Customizable layout — Toggle visibility of stats panels, license table columns, recent activity, and security overview.
- Command palette —
Ctrl+K/Cmd+Kto search licenses, navigate pages. - Right sheet detail panel — Click a license row to see full details, device list, and audit history in a slide-in panel.
- Multi-select bulk actions — Shift-click to select ranges, batch activate/suspend/delete.
- Real-time security log streaming — Server-sent events for live event updates.
- First-run setup wizard — Interactive CLI wizard generates Ed25519 keypairs, writes
.env, creates the admin account, seeds default license types (Trial, Standard, Premium, Lifetime) and an example application. Password strength meter with real-time validation. - Database auto-maintenance — Idempotent SQL migration engine runs on startup. Scheduled background cleanup of old security events, audit logs, resolved alerts, expired licenses, and stale clients. Retention periods are configurable. Includes SQLite VACUUM.
- Audit trail — Every admin action is logged with user ID, IP address, target, and timestamp.
- Define a license type — Set duration, device limit, and feature flags.
- Create an application — Scope licenses to a specific product.
- Generate licenses — Single key or batch, assigned to a type and application.
- Integrate the SDK — The client calls
init()→login()→heartbeat(). - Validate on each request — Server checks HWID binding, expiration, blacklists, license status, and device capacity.
- Monitor activity — Security events, threat alerts, and analytics are available in the dashboard.
| Component | Technology | Role |
|---|---|---|
| Backend | Python 3.8+, Flask 3.1 | HTTP server and routing |
| ORM | SQLAlchemy with Flask-SQLAlchemy 3.1 | Database abstraction |
| Database | SQLite (default), any SQLAlchemy-compatible URI | Data storage |
| Templates | Jinja2 | Server-side HTML rendering |
| UI design | Custom design system | Geist fonts, CSS custom properties, 8pt grid |
| Icons | Material Symbols | UI iconography |
| Charts | Chart.js 4.4 | Security analytics visualizations |
| Auth (dashboard) | Flask-Login + bcrypt | Admin session management |
| Auth (API) | HMAC-SHA256 + Ed25519 | Request signing and verification |
| Encryption | AES-256-GCM (PyCryptodome) | Payload encryption |
| Key exchange | X25519 (PyNaCl) + HKDF-SHA256 | Ephemeral keypairs for envelope key derivation |
| Signing | Ed25519 (PyNaCl) | Server response signing |
| Rate limiting | Flask-Limiter | Per-endpoint request caps |
| CSRF | Flask-WTF | Dashboard form protection |
| WSGI server | Waitress | Production serving |
| Environment | python-dotenv | .env file loading |
| Compression | Flask-Compress | Gzip response compression |
| SDK | Single-file Python | Client integration |
| Variable | Required | Default | Purpose |
|---|---|---|---|
SECRET_KEY |
Yes | — | 64-character hex string (32 bytes) used for AES key derivation, HMAC request signing, and client ID encryption |
ED25519_PRIVATE_KEY |
Yes | — | Ed25519 signing key for server response signatures. The SDK verifies these signatures before processing responses. |
ED25519_PUBLIC_KEY |
Yes | — | Ed25519 verify key, sent to clients during the init handshake |
DATABASE_URI |
No | SQLite path | Any SQLAlchemy-compatible URI. Defaults to sqlite:///server/database/keyvory.db |
SERVER_HOST |
No | 127.0.0.1 |
Bind address for the server |
SERVER_PORT |
No | 5000 |
Port for the server |
SESSION_COOKIE_SECURE |
No | false |
Set to true when serving behind HTTPS |
These keys are stored in the system_configs table and configurable via the dashboard:
| Key | Default | Description |
|---|---|---|
LICENSE_PREFIX |
KEYVORY |
Prefix for generated license keys |
LICENSE_KEY_SEGMENTS |
4 |
Number of 5-character segments after the prefix |
MESSAGE_TIMEOUT |
30 |
Max allowed clock skew (seconds) for API requests |
DB_CLEANER_INTERVAL |
1 |
How often the database cleaner runs (days) |
SECURITY_EVENT_RETENTION |
30 |
Purge security events older than this (days) |
AUDIT_LOG_RETENTION |
90 |
Purge audit logs older than this (days) |
SECURITY_ALERT_RETENTION |
60 |
Purge resolved alerts older than this (days) |
LICENSE_RETENTION |
30 |
Purge expired licenses older than this (days) |
CLIENT_RETENTION |
90 |
Purge inactive clients older than this (days) |
The admin dashboard at /admin covers the full license management workflow.
| Route | Purpose |
|---|---|
/admin/ |
Dashboard with stats, recent activity, quick actions |
/admin/licenses |
License table with search, multi-select, pagination. Click a row for the detail sheet. |
/admin/licenses/add |
Create single licenses or generate up to 500 in batch |
/admin/applications |
Manage applications (create, edit, toggle active, delete) |
/admin/license-types |
Manage license type templates with features and defaults |
/admin/groups |
Manage license groups with bulk suspend/activate/extend |
/admin/blacklist |
IP blacklist management |
/admin/blacklist/devices |
Device (HWID) blacklist management |
/admin/security-logs |
Security event log with filters, export, threat detection, and analytics |
/admin/customize_dashboard |
Toggle dashboard panels and table columns |
/admin/profile |
Change password |
/admin/api/users/unlock |
Manually unlock a locked admin account (POST, 10/min rate limit) (I know thats dumb but remove it if you want) |
The REST API lives under /api/v1/. Each request requires the following headers:
| Header | Description |
|---|---|
X-Client-ID |
Client identifier, AES-encrypted with the shared secret |
X-Timestamp |
Unix timestamp. Rejected if more than 30 seconds from server time. |
X-Signature |
HMAC-SHA256 of the request body, client ID, and timestamp |
X-Signature-Version |
Must be "2" |
X-Device-ID |
SHA-256 hardware fingerprint |
X-Platform |
Platform identifier (e.g., Windows, Linux) |
X-Client-Version |
SDK version string |
| Method | Endpoint | Rate Limit | Description |
|---|---|---|---|
| POST | /api/v1/license/init |
30/min | Bootstrap handshake. Returns AES-encrypted session key, X25519 private key (encrypted), server Ed25519 and X25519 public keys. Client uses X25519 for all subsequent envelope encryption. |
| POST | /api/v1/license/login |
20/min | Validate a license key and bind the hardware ID. Returns license type, expiration, features, and device limit inside a sealed X25519 envelope. |
| POST | /api/v1/license/heartbeat |
120/min | Keepalive ping with optional license re-validation. Returns {"status": "ok"} or {"status": "revoked", "reason": "..."} inside a sealed X25519 envelope. |
| POST | /api/v1/license/info |
30/min | Query license metadata without modifying the HWID lock. Caller must own the license (client_id match enforced). |
| POST | /api/v1/license/rotate-key |
10/min | Request a new AES session key. The old key encrypts the new key in transit. |
The SDK is a single file: example/python/keyvory_client.py. Copy it into your project alongside three dependencies (pycryptodome, requests, pynacl — all already in requirements.txt).
from keyvory_client import KeyvoryClient, LicenseStatus
with KeyvoryClient(
server_url="http://localhost:5000",
secret_key="your-secret-key", # same SECRET_KEY from server .env
) as client:
# 1. Handshake — required before any other call
if not client.initialize():
raise RuntimeError("Cannot reach license server")
# 2. Validate a license key
result: LicenseStatus = client.login("KEYVORY-XXXXX-XXXXX-XXXXX-XXXXX")
if result.is_valid:
print(f"License type: {result.license_type}")
print(f"Expires: {result.expiration_date}")
print(f"Max devices: {result.max_devices}")
if result.has_feature("export_pdf"):
enable_export()
else:
print(f"Denied: {result.status} ({result.code})")
# 3. Keep the session alive
while app_running:
do_work()
client.heartbeat(license_key="KEYVORY-XXXXX-XXXXX-XXXXX-XXXXX")SDK anti-tamper protections (enabled automatically):
- Module integrity — SHA-256 hash of the SDK source file at import time. Detects runtime patching.
- Debugger detection —
sys.gettrace()checked before every sensitive operation. - Timing anomaly — Measures SHA-256 computation time. Threshold: 5ms. Catches single-step debuggers.
- HMAC state chain — Session state (key, version, initialized flag) is HMAC-chained. Detects in-memory tampering between calls.
- Automatic key rotation — Session key rotates every 100 heartbeats via the server.
Full SDK reference: example/python/README.md
cd example/python
KEYVORY_SERVER=http://localhost:5000
KEYVORY_SECRET=your_secert_key
python example_client.pyPrompts for a license key and walks through the full flow: init handshake, login validation, info query, heartbeat keepalive, and session key rotation.
Migrations are idempotent SQL files in server/migrations/. They are applied automatically on startup.
To add a migration:
- Create a file with a numeric prefix (e.g.,
002_add_column_x.sql) - Write standard SQL statements separated by
; - The engine tracks applied versions in a
schema_migrationstable and only runs new ones
python run.pyUses Waitress, a production-grade WSGI server with multi-threaded serving (threads = CPU count x 2). Suitable for low-to-medium traffic without a reverse proxy.
server {
listen 443 ssl;
server_name licenses.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
}
}Set SESSION_COOKIE_SECURE=true in .env when terminating TLS at the proxy.
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Provide these via environment variables or a mounted .env file
# ENV SECRET_KEY="your-64-char-hex"
# ENV ED25519_PRIVATE_KEY="your-hex-key"
# ENV ED25519_PUBLIC_KEY="your-hex-key"
EXPOSE 5000
CMD ["python", "run.py"]keyvory/
├── run.py # Entry point: setup wizard + Waitress
├── requirements.txt # Python dependencies
├── server/
│ ├── app.py # Flask app factory, security headers
│ ├── first_run.py # Interactive CLI setup wizard
│ ├── extensions.py # Flask-Limiter instance
│ ├── models/ # SQLAlchemy models
│ │ ├── license.py # License keys, HWID, features, metadata
│ │ ├── client.py # Client sessions with encrypted keys + X25519 public key
│ │ ├── user.py # Admin users (Flask-Login)
│ │ ├── application.py # Application scoping
│ │ ├── license_type.py # License type templates
│ │ ├── license_group.py # License groups
│ │ ├── blacklist.py # IP and device blacklists
│ │ ├── security_event.py # Security event log
│ │ ├── security_alert.py # Security alerts
│ │ ├── audit_log.py # Admin audit trail
│ │ └── system_config.py # Key-value configuration store
│ ├── routes/
│ │ ├── api.py # Sealed envelope REST API
│ │ ├── admin.py # Dashboard routes and JSON admin API
│ │ └── auth.py # Login/logout with account lockout
│ ├── services/
│ │ ├── license_service.py # License validation, HWID locking, CRUD
│ │ ├── security_log_service.py # Event logging, threat detection, alerts
│ │ ├── audit_service.py # Admin audit trail
│ │ ├── auth_service.py # Password hashing and verification
│ │ ├── config_service.py # System config get/set
│ │ └── db_maintenance.py # Migration engine + scheduled cleanup
│ ├── utils/
│ │ ├── encryption.py # AES-GCM, X25519 key exchange, Ed25519, HKDF, nonce store
│ │ └── security.py # Auth decorator, IP blocker, signature verification
│ ├── templates/ # Jinja2 templates
│ ├── static/
│ │ ├── css/ # Design system, components, layout
│ │ ├── fonts/ # Geist Sans + Geist Mono
│ │ └── js/ # app.js, command-palette.js, right-sheet.js
│ └── migrations/ # Idempotent SQL migration files
│ ├── 001_add_device_tracking.sql # device_id, platform, client_version (example)
└── example/python/
├── keyvory_client.py # Python SDK with anti-tamper
├── example_client.py # Interactive CLI demo
└── README.md # SDK documentation
See CONTRIBUTING.md for development setup, coding conventions, and how to submit pull requests.
Bug reports and feature requests are welcome via GitHub Issues.
MIT — see LICENSE for details.