158 lines
5.3 KiB
Python
158 lines
5.3 KiB
Python
import hashlib
|
|
import uuid
|
|
import subprocess
|
|
import platform
|
|
import os
|
|
import base64
|
|
import sys
|
|
|
|
# Simple Secret Salt - In a real app, obfuscate this or use asymmetric keys (RSA)
|
|
# For this level, a symmetric secret is "good enough" for basic protection.
|
|
SECRET_SALT = "ProBackup_Secret_Salt_2026_!@#"
|
|
|
|
# Flag to hide console window on Windows
|
|
CREATE_NO_WINDOW = 0x08000000
|
|
|
|
class LicenseManager:
|
|
@staticmethod
|
|
def _get_base_dir():
|
|
if getattr(sys, 'frozen', False):
|
|
return os.path.dirname(sys.executable)
|
|
return os.path.dirname(os.path.abspath(__file__))
|
|
|
|
@staticmethod
|
|
def _get_license_path():
|
|
return os.path.join(LicenseManager._get_base_dir(), "license.dat")
|
|
|
|
@staticmethod
|
|
def save_license(key):
|
|
try:
|
|
with open(LicenseManager._get_license_path(), "w") as f:
|
|
f.write(key.strip())
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
@staticmethod
|
|
def load_license():
|
|
path = LicenseManager._get_license_path()
|
|
if not os.path.exists(path):
|
|
return None
|
|
try:
|
|
with open(path, "r") as f:
|
|
return f.read().strip()
|
|
except:
|
|
return None
|
|
@staticmethod
|
|
def get_motherboard_id():
|
|
"""Gets the motherboard UUID using WMIC (Windows only)."""
|
|
try:
|
|
result = subprocess.run(
|
|
['wmic', 'csproduct', 'get', 'uuid'],
|
|
capture_output=True, text=True, timeout=5,
|
|
creationflags=CREATE_NO_WINDOW
|
|
)
|
|
lines = result.stdout.strip().split('\n')
|
|
for line in lines:
|
|
line = line.strip()
|
|
if line and line != 'UUID':
|
|
return line
|
|
except:
|
|
pass
|
|
return None
|
|
|
|
@staticmethod
|
|
def get_processor_id():
|
|
"""Gets the processor ID using WMIC (Windows only)."""
|
|
try:
|
|
result = subprocess.run(
|
|
['wmic', 'cpu', 'get', 'processorid'],
|
|
capture_output=True, text=True, timeout=5,
|
|
creationflags=CREATE_NO_WINDOW
|
|
)
|
|
lines = result.stdout.strip().split('\n')
|
|
for line in lines:
|
|
line = line.strip()
|
|
if line and line != 'ProcessorId':
|
|
return line
|
|
except:
|
|
pass
|
|
# Fallback to platform.processor()
|
|
return platform.processor()
|
|
|
|
@staticmethod
|
|
def _hash_to_hwid(raw_id):
|
|
"""Converts raw ID string to formatted HWID."""
|
|
hwid_hash = hashlib.sha256(raw_id.encode()).hexdigest().upper()
|
|
return "{}-{}-{}-{}".format(hwid_hash[:4], hwid_hash[4:8], hwid_hash[8:12], hwid_hash[12:16])
|
|
|
|
@staticmethod
|
|
def get_hardware_id():
|
|
"""
|
|
Returns hardware ID based on combination of Motherboard UUID + Processor ID.
|
|
This is for DISPLAY purposes to customer.
|
|
"""
|
|
mb_id = LicenseManager.get_motherboard_id() or "UNKNOWN_MB"
|
|
proc_id = LicenseManager.get_processor_id() or "UNKNOWN_CPU"
|
|
|
|
# Combine both IDs for display
|
|
combined = "COMBO-{}-{}".format(mb_id, proc_id)
|
|
return LicenseManager._hash_to_hwid(combined)
|
|
|
|
@staticmethod
|
|
def get_mb_hwid():
|
|
"""Returns HWID based on motherboard only."""
|
|
mb_id = LicenseManager.get_motherboard_id() or "UNKNOWN_MB"
|
|
return LicenseManager._hash_to_hwid("MB-{}".format(mb_id))
|
|
|
|
@staticmethod
|
|
def get_cpu_hwid():
|
|
"""Returns HWID based on processor only."""
|
|
proc_id = LicenseManager.get_processor_id() or "UNKNOWN_CPU"
|
|
return LicenseManager._hash_to_hwid("CPU-{}".format(proc_id))
|
|
|
|
@staticmethod
|
|
def generate_signature(hwid):
|
|
"""Generates the expected signature for a given HWID."""
|
|
data = "{}|{}".format(hwid, SECRET_SALT)
|
|
return hashlib.sha256(data.encode()).hexdigest().upper()
|
|
|
|
@staticmethod
|
|
def validate_license(key, hwid=None):
|
|
"""
|
|
Validates license with flexible hardware check.
|
|
Returns tuple: (is_valid, status)
|
|
- status: 'valid', 'mb_changed', 'cpu_changed', 'both_changed', 'invalid'
|
|
"""
|
|
if not key:
|
|
return False, 'invalid'
|
|
|
|
key = key.strip().upper()
|
|
|
|
# Check against combined HWID (exact match - preferred)
|
|
combined_hwid = LicenseManager.get_hardware_id()
|
|
expected_combined = LicenseManager.generate_signature(combined_hwid)[:32]
|
|
if key == expected_combined:
|
|
return True, 'valid'
|
|
|
|
# Check against motherboard only
|
|
mb_hwid = LicenseManager.get_mb_hwid()
|
|
expected_mb = LicenseManager.generate_signature(mb_hwid)[:32]
|
|
mb_match = (key == expected_mb)
|
|
|
|
# Check against processor only
|
|
cpu_hwid = LicenseManager.get_cpu_hwid()
|
|
expected_cpu = LicenseManager.generate_signature(cpu_hwid)[:32]
|
|
cpu_match = (key == expected_cpu)
|
|
|
|
# If either matches, license is valid (single hardware change allowed)
|
|
if mb_match:
|
|
return True, 'cpu_changed' # MB matches, CPU must have changed
|
|
if cpu_match:
|
|
return True, 'mb_changed' # CPU matches, MB must have changed
|
|
|
|
# Neither matches - could be both changed or completely invalid key
|
|
return False, 'both_changed'
|
|
|
|
|