๐ Secure offline licensing system with beautiful CLI, ECDSA signatures, and hardware fingerprinting
Project description
๐ LicensingPy - Professional Offline Licensing System
A professional, secure offline licensing solution with beautiful CLI, ECDSA signatures, and hardware fingerprinting.
โจ Features
- ๐ ECDSA P-256 Signatures - Cryptographically secure license verification
- ๐ฅ๏ธ Hardware Fingerprinting - Bind licenses to specific machines (MAC, disk, CPU, system, composite)
- ๐งฉ Component-Based Licensing - Separate licenses for different modules/components
- ๐ฑ Secure Preseed System - File-based secret management with SHA-256 hashing
- ๐จ Beautiful Rich CLI - Colorful, interactive command-line interface with progress bars
- ๐ง Cross-Platform - Windows, Linux, macOS with native fallbacks
- ๐ Offline Operation - No internet connection required for license operations
- ๐ก๏ธ Tamper Resistant - Licenses cannot be modified, copied, or forged
- โก Auto-Verification - Automatic license discovery and batch validation
- ๐ฆ Zero Dependencies - Optional hardware detection libraries with native fallbacks
- ๐งช Comprehensive Tests - 111+ test cases with high coverage
- ๐ Complete Documentation - Detailed guides and API documentation
๐ฆ Installation
Using pip (Recommended)
pip install licensingpy
Using Poetry
poetry add licensingpy
With Optional Hardware Detection
For enhanced hardware detection capabilities:
pip install "licensingpy[hardware]"
# or
poetry add licensingpy -E hardware
Development Installation
git clone https://github.com/licensingpy/licensingpy.git
cd licensingpy
poetry install --with dev,test
๐ Quick Start Guide
Step 1: Generate Preseed File
The preseed file contains secret content that secures your licenses:
licensingpy generate-preseed --project-name "MyAwesomeApp" --description "Production preseed for MyApp" --output my_app_preseed.json
Output:
โ Preseed file generated: my_app_preseed.json
โ Secret length: 64 characters
โ File size: 285 bytes
๐ SECURITY NOTES:
โข Keep my_app_preseed.json secure and confidential
โข Do NOT commit my_app_preseed.json to version control
โข Back up my_app_preseed.json safely
Step 2: Generate Cryptographic Keys
licensingpy generate-keys --format json --output my_app_keys.json
Output:
โ Key pair generated successfully
โ Keys saved to my_app_keys.json
๐ SECURITY NOTES:
- Keep the private key secure and confidential
- The public key can be distributed with your application
Step 3: Generate a License
licensingpy generate-license --private-key my_app_keys.json --preseed-file my_app_preseed.json --app-name "MyAwesomeApp" --version "2.1.0" --component-name "CoreEngine" --customer "Acme Corporation" --expires "2025-12-31" --output customer_license.txt
Output:
โ Loaded preseed from: my_app_preseed.json
Generating license (expires: 2025-12-31)...
Hardware fingerprint (composite): 4e120bb4a65e...
License generated successfully!
License Details:
Fingerprint Type: composite
Expiry Date: 2025-12-31
Component Name: CoreEngine
App Name: MyAwesomeApp
App Version: 2.1.0
Customer: Acme Corporation
โ License saved to: customer_license.txt
Step 4: Verify the License
licensingpy verify-license --public-key my_app_keys.json --preseed-file my_app_preseed.json --license customer_license.txt --verbose
Output:
โ Loaded preseed from: my_app_preseed.json
โ Loaded license from: customer_license.txt
LICENSE IS VALID AND ACTIVE
โ Signature verification: PASSED
โ Hardware fingerprint: MATCHED
โ License expiry: NOT EXPIRED
โ Component verification: PASSED
๐ป Using in Your Python Code
Basic License Verification
from licensing import LicenseManager, PreseedGenerator
# Load your keys and preseed
def load_app_credentials():
"""Load your app's licensing credentials."""
import json
# Load public key
with open('my_app_keys.json', 'r') as f:
keys = json.load(f)
public_key = keys['public_key']
# Load and hash preseed
preseed_hash = PreseedGenerator.load_preseed_from_file('my_app_preseed.json')
return public_key, preseed_hash
# Verify a single license
def verify_license(license_string):
"""Verify a license string."""
try:
public_key, preseed_hash = load_app_credentials()
manager = LicenseManager(public_key, preseed_hash)
# Verify the license
license_data = manager.verify_license(license_string)
print("โ
License is valid!")
print(f"App: {license_data.get('app_name')}")
print(f"Component: {license_data.get('component_name')}")
print(f"Customer: {license_data.get('customer')}")
print(f"Expires: {license_data.get('expiry')}")
return True
except Exception as e:
print(f"โ License verification failed: {e}")
return False
# Example usage
if __name__ == "__main__":
# Load license from file
with open('customer_license.txt', 'r') as f:
license_string = f.read().strip()
if verify_license(license_string):
print("๐ Application can start!")
else:
print("๐ซ Application access denied!")
Auto-Discovery License Verification
from licensing import auto_verify_licenses
def check_all_licenses():
"""Automatically find and verify all licenses in current directory."""
# Auto-discover and verify licenses
results = auto_verify_licenses()
if "error" in results:
print(f"โ Error: {results['error']}")
return False
summary = results['summary']
print(f"๐ License Summary:")
print(f" Total found: {summary['total_licenses']}")
print(f" โ
Valid: {summary['valid_count']}")
print(f" โ Invalid: {summary['invalid_count']}")
print(f" โฐ Expired: {summary['expired_count']}")
# Show valid licenses
for license_data in results['valid_licenses']:
print(f"\n๐ Valid License:")
print(f" App: {license_data.get('app_name', 'N/A')}")
print(f" Component: {license_data.get('component_name', 'N/A')}")
print(f" Customer: {license_data.get('customer', 'N/A')}")
print(f" Expires: {license_data.get('expiry', 'N/A')}")
return summary['valid_count'] > 0
# Example usage
if __name__ == "__main__":
if check_all_licenses():
print("๐ Valid licenses found - application authorized!")
else:
print("๐ซ No valid licenses found - access denied!")
Component-Specific License Checking
from licensing import LicenseManager, PreseedGenerator
class ComponentLicenseManager:
"""Manage licenses for different application components."""
def __init__(self, public_key_file, preseed_file):
# Load credentials
import json
with open(public_key_file, 'r') as f:
keys = json.load(f)
self.public_key = keys['public_key']
self.preseed_hash = PreseedGenerator.load_preseed_from_file(preseed_file)
self.manager = LicenseManager(self.public_key, self.preseed_hash)
# Cache for verified licenses
self.verified_components = {}
def verify_component_license(self, license_file, required_component):
"""Verify license for a specific component."""
# Check cache first
if required_component in self.verified_components:
return self.verified_components[required_component]
try:
# Load license
with open(license_file, 'r') as f:
license_string = f.read().strip()
# Verify license
license_data = self.manager.verify_license(license_string)
# Check component match
license_component = license_data.get('component_name', '')
if license_component != required_component:
print(f"โ Component mismatch: need '{required_component}', got '{license_component}'")
return False
# Cache successful verification
self.verified_components[required_component] = license_data
print(f"โ
Component '{required_component}' licensed to: {license_data.get('customer')}")
return True
except Exception as e:
print(f"โ Component '{required_component}' license verification failed: {e}")
return False
def require_license(self, component_name):
"""Decorator to require license for a component."""
def decorator(func):
def wrapper(*args, **kwargs):
if not self.verify_component_license('license.txt', component_name):
raise PermissionError(f"No valid license for component '{component_name}'")
return func(*args, **kwargs)
return wrapper
return decorator
# Example usage
license_manager = ComponentLicenseManager('my_app_keys.json', 'my_app_preseed.json')
@license_manager.require_license('DatabaseEngine')
def access_database():
"""This function requires a valid DatabaseEngine license."""
print("๐๏ธ Accessing database with licensed engine...")
# Your database code here
@license_manager.require_license('ReportGenerator')
def generate_reports():
"""This function requires a valid ReportGenerator license."""
print("๐ Generating reports with licensed engine...")
# Your reporting code here
# Use the licensed functions
try:
access_database() # Requires DatabaseEngine license
generate_reports() # Requires ReportGenerator license
except PermissionError as e:
print(f"๐ซ Access denied: {e}")
Advanced License Validation
from licensing import LicenseManager, PreseedGenerator
from datetime import datetime, timedelta
def advanced_license_check(license_file, preseed_file, public_key_file):
"""Advanced license validation with detailed reporting."""
try:
# Load credentials
import json
with open(public_key_file, 'r') as f:
keys = json.load(f)
public_key = keys['public_key']
preseed_hash = PreseedGenerator.load_preseed_from_file(preseed_file)
manager = LicenseManager(public_key, preseed_hash)
# Load license
with open(license_file, 'r') as f:
license_string = f.read().strip()
# Get license info without full validation
license_info = manager.get_license_info(license_string)
print("๐ License Information:")
print(f" App: {license_info.get('app_name', 'N/A')}")
print(f" Version: {license_info.get('app_version', 'N/A')}")
print(f" Component: {license_info.get('component_name', 'N/A')}")
print(f" Customer: {license_info.get('customer', 'N/A')}")
print(f" Issued: {license_info.get('issued', 'N/A')}")
print(f" Expires: {license_info.get('expiry', 'N/A')}")
# Check expiry details
days_left = manager.get_days_until_expiry(license_string)
if days_left is not None:
if days_left > 0:
print(f" โฐ Days remaining: {days_left}")
if days_left <= 30:
print(" โ ๏ธ License expiring soon!")
else:
print(f" ๐ License expired {abs(days_left)} days ago")
# Show status details
status = license_info.get('status', {})
print(f"\n๐ Verification Status:")
print(f" Signature: {'โ
VALID' if not status.get('signature_invalid') else 'โ INVALID'}")
print(f" Hardware: {'โ
MATCH' if status.get('hardware_matches') else 'โ MISMATCH'}")
print(f" Expiry: {'โ
ACTIVE' if not status.get('is_expired') else 'โ EXPIRED'}")
print(f" Preseed: {'โ
VALID' if status.get('preseed_valid') else 'โ INVALID'}")
# Attempt full verification
print(f"\n๐ก๏ธ Full Verification:")
try:
verified_license = manager.verify_license(license_string)
print("โ
LICENSE IS FULLY VALID AND ACTIVE")
return True
except Exception as e:
print(f"โ Full verification failed: {e}")
return False
except Exception as e:
print(f"โ License check failed: {e}")
return False
# Example usage
if __name__ == "__main__":
is_valid = advanced_license_check(
license_file='customer_license.txt',
preseed_file='my_app_preseed.json',
public_key_file='my_app_keys.json'
)
if is_valid:
print("\n๐ Application startup authorized!")
else:
print("\n๐ซ Application startup denied!")
๐ ๏ธ CLI Reference
Generate Preseed File
licensingpy generate-preseed [OPTIONS]
Options:
-o, --output PATH Output file (default: preseed.json)
-l, --length INTEGER Secret length in characters (default: 64)
--project-name TEXT Project name for metadata
--description TEXT Description for metadata
Generate Keys
licensingpy generate-keys [OPTIONS]
Options:
-o, --output PATH Output file for keys
--format [json|text] Output format (default: text)
Generate License
licensingpy generate-license [OPTIONS]
Required:
-k, --private-key PATH Private key file
-p, --preseed-file PATH Preseed file
Options:
-e, --expires TEXT Expiry date (YYYY-MM-DD) or days (30d)
-f, --fingerprint-type Hardware fingerprint type
-t, --target-hardware PATH Generate for specific hardware
--app-name TEXT Application name
--version TEXT Application version
--customer TEXT Customer name
-c, --component-name TEXT Component/module name
-o, --output PATH Output license file
Verify License
licensingpy verify-license [OPTIONS]
Required:
-k, --public-key PATH Public key file
-p, --preseed-file PATH Preseed file
-l, --license PATH License file or string
Options:
--skip-hardware Skip hardware verification
--skip-expiry Skip expiry verification
-v, --verbose Show detailed information
Demo Workflow
licensingpy demo
๐ File Structure
After following the quick start guide, you'll have:
your_project/
โโโ my_app_preseed.json # Secret preseed file (keep secure!)
โโโ my_app_keys.json # Public/private keys
โโโ customer_license.txt # Generated license
โโโ your_application.py # Your app with license verification
๐ Security Best Practices
1. Preseed File Security
- โ Keep preseed files secure - treat like passwords
- โ Never commit to version control - add to .gitignore
- โ Back up safely - store in secure location
- โ Use different preseeds for different products/versions
2. Key Management
- โ Private keys - Keep secret, use for license generation only
- โ Public keys - Can be distributed with your application
- โ Separate environments - Different keys for dev/staging/prod
3. License Distribution
- โ Unique licenses - Generate separate license for each customer
- โ Component isolation - Use different component names for modules
- โ Expiry dates - Set appropriate expiration dates
- โ Hardware binding - Bind to customer's specific hardware
4. Application Integration
- โ Fail securely - Deny access if license verification fails
- โ Cache verification - Avoid re-verifying same license repeatedly
- โ Component separation - Check licenses for specific features
- โ User feedback - Provide clear messages for license issues
๐ Troubleshooting
License Verification Fails
Hardware Mismatch:
# Skip hardware check for testing
licensingpy verify-license --skip-hardware ...
License Expired:
# Check expiry date
licensingpy verify-license --verbose ...
Invalid Signature:
- Ensure you're using the correct preseed file
- Verify the public key matches the private key used for generation
Auto-Discovery Issues
No licenses found:
- Ensure license files are in current directory
- Check file naming:
license.txt,*.license, etc.
No keys found:
- Ensure key files are present:
keys.json,public_key.txt, etc.
๐ Support
For issues and questions:
- Check this README for common solutions
- Review the example code above
- Test with the demo command:
licensingpy demo - Verify your file structure and permissions
๐ฏ Example Project Structure
# main.py - Your application entry point
from licensing import LicenseManager, PreseedGenerator
def startup_license_check():
"""Check license before starting application."""
try:
# Load credentials
import json
with open('app_keys.json', 'r') as f:
public_key = json.load(f)['public_key']
preseed_hash = PreseedGenerator.load_preseed_from_file('app_preseed.json')
manager = LicenseManager(public_key, preseed_hash)
# Verify license
with open('license.txt', 'r') as f:
license_string = f.read().strip()
license_data = manager.verify_license(license_string)
print(f"โ
Licensed to: {license_data.get('customer')}")
print(f"๐ฑ App: {license_data.get('app_name')} v{license_data.get('app_version')}")
print(f"๐งฉ Component: {license_data.get('component_name')}")
return True
except Exception as e:
print(f"โ License verification failed: {e}")
return False
if __name__ == "__main__":
if startup_license_check():
print("๐ Starting application...")
# Your application code here
else:
print("๐ซ Application startup denied - invalid license")
exit(1)
๐ You're now ready to integrate secure offline licensing into your Python applications!
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 licensingpy-1.0.0.tar.gz.
File metadata
- Download URL: licensingpy-1.0.0.tar.gz
- Upload date:
- Size: 32.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.4 CPython/3.11.13 Linux/6.11.0-1018-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
19d20059cccbd67a9ae6fdd4981ab732f801618ffd589ae8fc080945ab9b7b7c
|
|
| MD5 |
97cb1978f24c1e3701ca506f9a6e2316
|
|
| BLAKE2b-256 |
e692a8372f50686010061df09f4ff82c9406a15b8dc2bad471ee8b9f1d8f2bfe
|
File details
Details for the file licensingpy-1.0.0-py3-none-any.whl.
File metadata
- Download URL: licensingpy-1.0.0-py3-none-any.whl
- Upload date:
- Size: 31.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.4 CPython/3.11.13 Linux/6.11.0-1018-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dccbbc9b97dec05f29cc60c4356840fbcd512a4e48c18fd99a911a148c7accd8
|
|
| MD5 |
7dcdae98eaf96b2928afd506100246b9
|
|
| BLAKE2b-256 |
5cf1cb6b86b4c283b1333f265c2b47507e5a1310e51761a8ce7f4c8d2ba04519
|