EasyLic solves the problem of software piracy by requiring real-time license checks, unlike offline systems that can be easily cracked or shared. It provides a secure, cryptographic license server built with FastAPI, offering session management, revocation, and comprehensive security features. If you're new, start with the Quick Start below.
Important Security Notes: Of course, the code should be compiled into a binary/obfuscated, and license checks should be placed not in one place, but throughout the code. We also recommend adding self-checks for code integrity via file hash to prevent tampering.
- Features
- Quick Start
- Installation
- CLI Usage
- Troubleshooting
- Configuration
- API Reference
- Deployment
- Concepts and Security Overview
- Architecture
- FAQ
- Development
- Contributing
- Changelog
- License
EasyLic provides everything you need for secure software licensing:
- Real-Time Validation: Licenses require online checks, allowing instant revocation and preventing piracy.
- Multi-Device Control: Set limits on concurrent usage to prevent license sharing.
- Admin Dashboard: Web interface for creating, managing, and revoking licenses.
- Easy Integration: Python client library with auto-renewal for seamless app integration.
- Decorator-Based Protection: Simple decorators to protect functions and methods (
@requires_active_license,@license_protected,@license_retry_on_fail). - Docker Ready: Simple containerized deployment.
- Cryptographic Protection: Advanced security with digital signatures and encryption (see Concepts and Security Overview for details).
Before starting, ensure you have Python 3.8+ installed.
-
Install EasyLic:
pip install easylic
-
Set Environment Variables:
export EASYLIC_ADMIN_PASSWORD=your_secure_password_here # Choose a strong password for admin access export EASYLIC_SERVER_HOST=0.0.0.0 # Bind to all interfaces (or 127.0.0.1 for local only) export EASYLIC_SERVER_PORT=8000 # Default port
-
Generate Keys (required):
easylic keygen
This creates cryptographic keys in
./easylic/server. If you see "Permission denied", ensure the directory exists and is writable:mkdir -p ./easylic/server. Or set an alternative directory via--keys-dir. -
Start Server:
easylic serve
The server starts at http://localhost:8000. Visit http://localhost:8000/admin for the admin panel (login with your admin password).
Integrate licensing into your Python app:
from easylic.client.client import LicenseClient
# Load license and connect to server
client = LicenseClient()
# Start a secure session
session_id = client.start_session()
print(f"Session started: {session_id}")
# Check if license is active
if client.is_license_active():
print("License is valid - your app can run")
else:
print("License invalid - exit app")
# Run with auto-renewal in background
client.start_in_thread()
# Your app logic here
while client.is_license_active():
# App runs while license is active
passThe easiest way to protect your functions is using decorators:
from easylic import LicenseClient, requires_active_license
client = LicenseClient()
# Protect a function - raises ValidationError if license is inactive
@requires_active_license(client, "Premium feature requires active license")
def premium_feature():
return "Premium feature executed!"
# Graceful handling - returns None if license is inactive
@requires_active_license(client, "Optional feature unavailable", raise_exception=False)
def optional_feature():
return "Optional feature executed!"
# Class method protection
class MyService:
def __init__(self):
self.client = LicenseClient()
@requires_active_license("client", "Service requires license")
def protected_method(self):
return "Protected method executed"
# Retry logic for critical operations
from easylic import license_retry_on_fail
@license_retry_on_fail(client, max_retries=3)
def critical_operation():
return "Critical operation completed"See docs/decorators.md for full documentation.
Use the web admin panel at http://localhost:8000/admin to create licenses interactively.
Or via CLI:
easylic generatorOr via API: See API Reference for the POST /generate_license endpoint.
Note: The generated license is a JSON file that should be distributed to clients apps.
- Python 3.8+
EasyLic is published on PyPI and can be installed directly:
pip install easylicgit clone https://github.com/tphlru/easylic
cd easylic
pip install -e .EasyLic includes command-line tools for key management and server operations:
easylic keygen: Generate cryptographic keys for the server. Run this first to createserver.keyandserver.pubinEASYLIC_KEYS_DIR.easylic serve: Start the license server with configured host/port. Requires keys from keygen.easylic generator: Interactive tool to create licenses. Prompts for details like license ID, validity, and features.
Run easylic --help for full options. All commands respect environment variables like EASYLIC_KEYS_DIR.
Common issues and solutions:
- "Key not found" or "Permission denied" on keygen: Ensure
EASYLIC_KEYS_DIR(default./easylic/server) exists and is writable. - Server won't start: Check if port is free (
lsof -i :8000). SetEASYLIC_SERVER_PORTto a different value if needed. - Client connection fails: Verify
server_urlin client config points to running server (e.g.,http://localhost:8000). - License invalid: Check license file format and dates. Use admin panel to generate test licenses.
- Rate limited: Wait a minute; the server limits start attempts to 10/minute per license.
EasyLic uses environment variables for configuration:
| Variable | Default | Description |
|---|---|---|
EASYLIC_SERVER_HOST |
127.0.0.1 |
Server bind address |
EASYLIC_SERVER_PORT |
8000 |
Server port |
EASYLIC_ADMIN_PASSWORD |
admin |
Password for admin operations |
LOG_LEVEL |
INFO |
Logging level (DEBUG, INFO, WARNING, ERROR) |
EASYLIC_KEYS_DIR |
./easylic/server |
Directory for server keys |
The client can be configured via ClientConfig object or environment variables:
from easylic.client.client import LicenseClient
from easylic.common.models import ClientConfig
config = ClientConfig(
server_url="http://localhost:8000",
license_file="/path/to/license.json",
renew_interval=30, # seconds
log_level=20, # logging.INFO
)
client = LicenseClient(config)Health check endpoint.
Response:
{
"status": "healthy",
"timestamp": 1703123456
}Initiate a secure session.
Request:
{
"version": 1,
"license": {
"payload": {
"license_id": "lic-001",
"product": "MyApp",
"valid_from": 1704067200,
"valid_until": 1735689600,
"policy": {
"version": "1.0",
"max_sessions": 1
}
},
"signature": "hex-encoded-signature"
},
"client_pubkey": "hex-string",
"client_eph_pub": "hex-string",
"supported_features": {
"secure_channel": true,
"counter": true,
"pop": true,
"transcript_binding": true,
"rekey": true,
"proofs": true
}
}Response:
{
"session_id": "uuid",
"expires_at": 1703123456,
"protocol_version": 1,
"cipher_suite": "v1:ChaCha20Poly1305",
"required_features": {...},
"server_eph_pub": "hex-string",
"nonce_prefix": "hex-string",
"signature": "hex-string",
"transcript_hash": "hex-string",
"handshake_ciphertext": "hex-string",
"handshake_nonce": "hex-string"
}Renew an existing session.
Request:
{
"session_id": "uuid",
"ciphertext": "encrypted-renew-data",
"counter": 1
}Response:
{
"ciphertext": "encrypted-response",
"counter": 2,
"epoch_used": 0
}Revoke a license (admin only).
Request:
{
"payload": {
"license_id": "lic-001"
},
"signature": "admin-signature"
}Generate a new license (admin only).
Request:
{
"password": "your_admin_password",
"license_id": "lic-001",
"product": "MyApp",
"valid_from": 1704067200,
"valid_until": 1735689600,
"policy": {
"version": "1.0",
"max_sessions": 1,
"features": ["feature1", "feature2"]
}
}Response:
{
"license_id": "lic-001",
"product": "MyApp",
"valid_from": 1704067200,
"valid_until": 1735689600,
"policy": {
"version": "1.0",
"max_sessions": 1,
"features": ["feature1", "feature2"]
},
"signature": "hex-encoded-signature"
}Example cURL:
curl -X POST http://localhost:8000/generate_license \
-H "Content-Type: application/json" \
-d '{
"password": "your_admin_password",
"license_id": "lic-001",
"product": "MyApp",
"valid_from": 1704067200,
"valid_until": 1735689600,
"policy": {
"version": "1.0",
"max_sessions": 1,
"features": ["feature1", "feature2"]
}
}'docker build -t easylic .# Persistent keys
docker run -v $(pwd)/easylic/server:/home/app/easylic/server \
-e EASYLIC_ADMIN_PASSWORD=secure_password \
-p 8000:8000 easylic
# One-time (keys lost on stop)
docker run -p 8000:8000 easylic- Use strong
EASYLIC_ADMIN_PASSWORD - Store keys securely (volume mount or secret management) and create a backup
- Configure reverse proxy (nginx) for SSL termination
To get started, here are some core terms explained simply:
- Online Licensing: Requires internet connection for real-time validation, preventing piracy by allowing instant revocation and usage monitoring (unlike offline licenses that can be copied).
- Session: A temporary, secure connection between your app and the license server for validation. Sessions auto-renew and expire if the license is invalid.
- Cryptographic Security: Uses Ed25519 digital signatures (for proving authenticity) and ChaCha20Poly1305 encryption (for secure data transmission) to protect against tampering and eavesdropping.
- Proof of Possession: Clients prove they own the license without revealing secrets, using digital signatures.
- Revocation: Admins can instantly disable licenses, terminating all active sessions.
- Concurrent Sessions: Limits how many devices can use the same license simultaneously, preventing sharing.
EasyLic implements multiple layers of security to prevent licensing bypass and enforce license policies:
- Online Validation: Unlike offline licensing, EasyLic requires real-time server communication, enabling instant revocation and preventing license sharing across multiple concurrent sessions
- Session Concurrency Limits: The
max_sessionspolicy prevents a single license from being used simultaneously on multiple devices, blocking common piracy techniques - Mandatory Security Features: All clients must support secure_channel, counter, pop, transcript_binding, rekey, and proofs
- AEAD Encryption: ChaCha20Poly1305 for confidentiality and integrity
- Monotonic Counters: Prevent replay attacks and ensure message ordering
- Transcript Binding: Channel binding to handshake transcript hash
- Periodic Rekeying: Automatic key rotation every 10 renewals
- Proof-of-Possession: Ed25519 signatures for client authentication
- Rate Limiting: Prevents DoS and replay attacks on session establishment
- Session Limits: Hard limits on counter values to prevent nonce reuse
EasyLic consists of three main components:
- Client App: Integrates the
LicenseClientlibrary for session management and auto-renewal. Communicates securely with the server. - License Server (FastAPI): Handles validation, session storage, key management, and cryptographic operations. Core security logic resides here.
- Admin Panel (Web UI): Allows admins to generate licenses, revoke them, and monitor usage.
All components rely on shared cryptographic primitives (Ed25519 signatures and ChaCha20Poly1305 encryption) for secure communication.
Licenses follow a strict state machine:
INIT → ACTIVE → REKEY → ACTIVE → EXPIRED → REVOKED
- INIT: Newly issued license, not yet activated
- ACTIVE: Valid license with active sessions
- REKEY: Temporary state during key rotation (every 10 renewals)
- EXPIRED: License validity period has ended
- REVOKED: License has been administratively revoked
Q: Why use online licensing instead of offline?
A: Offline licenses can be easily copied and shared. Online licensing requires real-time server validation, enabling instant revocation, usage monitoring, and preventing piracy spread.
Q: How does it prevent concurrent usage?
A: Each license has a max_sessions limit. If a license is used on more devices than allowed, new sessions are rejected.
Q: What if the server is down?
A: Clients cache license validity briefly (less than 1 minute usually), but long outages require server availability. Plan for high availability in production.
Q: Can I use it with non-Python apps?
A: The client is Python-only, but you can integrate via API calls to the server endpoints.
Q: Common errors: "Connection failed"
A: Ensure server is running and server_url in client config points to the correct address (default: http://localhost:8000).
Q: "License invalid"
A: Check license file format, dates, and ensure it's not revoked via admin panel. Also the keys files on the server must be the same as those used when creating the license. If you reissue them, the server will not be able to validate previously issued licenses, so keep your backups safe!
Q: Permission denied on keygen
A: Try setting EASYLIC_KEYS_DIR to a writable path.
Run the test suite:
pytestCheck code quality:
ruff check .
mypy .
For development with auto-reload:
uvicorn easylic.server.core:app --reload- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow PEP 8 style guidelines
- Add tests for new features
- Update documentation for API changes
- Ensure all tests pass before submitting PR
- v0.0.0: Initial release with core licensing features. See git log for detailed changes.
- v0.1.0: Published on pip!
TPHL - All rights reserved.
This software may only be used in accordance with the terms of the license agreement.