import hashlib import uuid import subprocess import platform import os import base64 # 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_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' @staticmethod def save_license(key): try: with open("license.dat", "w") as f: f.write(key.strip()) return True except: return False @staticmethod def load_license(): if not os.path.exists("license.dat"): return None try: with open("license.dat", "r") as f: return f.read().strip() except: return None