Skip to main content

๐Ÿ” Secure offline licensing system with beautiful CLI, ECDSA signatures, and hardware fingerprinting

Project description

๐Ÿ” LicensingPy - Professional Offline Licensing System

PyPI version Python Support License: MIT Code style: black

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:

  1. Check this README for common solutions
  2. Review the example code above
  3. Test with the demo command: licensingpy demo
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

licensingpy-1.0.0.tar.gz (32.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

licensingpy-1.0.0-py3-none-any.whl (31.9 kB view details)

Uploaded Python 3

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

Hashes for licensingpy-1.0.0.tar.gz
Algorithm Hash digest
SHA256 19d20059cccbd67a9ae6fdd4981ab732f801618ffd589ae8fc080945ab9b7b7c
MD5 97cb1978f24c1e3701ca506f9a6e2316
BLAKE2b-256 e692a8372f50686010061df09f4ff82c9406a15b8dc2bad471ee8b9f1d8f2bfe

See more details on using hashes here.

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

Hashes for licensingpy-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dccbbc9b97dec05f29cc60c4356840fbcd512a4e48c18fd99a911a148c7accd8
MD5 7dcdae98eaf96b2928afd506100246b9
BLAKE2b-256 5cf1cb6b86b4c283b1333f265c2b47507e5a1310e51761a8ce7f4c8d2ba04519

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page