# license_manager.py import os import json import base64 import hashlib import uuid import platform import subprocess import sys from datetime import datetime try: from cryptography.hazmat.primitives.ciphers.aead import AESGCM except ImportError: raise ImportError("请先安装依赖: pip install cryptography") ENCRYPTION_KEY = b"0123456789abcdef0123456789abcdef" def get_app_dir(): if getattr(sys, 'frozen', False): return os.path.dirname(sys.executable) return os.path.dirname(os.path.abspath(__file__)) LICENSE_FILE = os.path.join(get_app_dir(), "license.lic") def get_machine_guid(): system = platform.system() if system == "Windows": try: import winreg with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Cryptography") as key: return winreg.QueryValueEx(key, "MachineGuid")[0] except Exception: pass elif system == "Linux": for path in ["/etc/machine-id", "/var/lib/dbus/machine-id"]: if os.path.exists(path): with open(path, "r", encoding="utf-8") as f: return f.read().strip() elif system == "Darwin": try: result = subprocess.run( ["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"], capture_output=True, text=True ) for line in result.stdout.split("\n"): if "IOPlatformUUID" in line: return line.split('"')[-2] except Exception: pass return str(uuid.getnode()) def get_machine_code(): guid = get_machine_guid() h = hashlib.sha256() h.update(b"LicenseSystem") h.update(guid.encode("utf-8")) return h.hexdigest() def decrypt_key(encrypted_key: str): try: data = base64.b64decode(encrypted_key) except Exception: raise ValueError("Base64 解码失败") if len(data) < 12: raise ValueError("密文太短") aesgcm = AESGCM(ENCRYPTION_KEY) nonce = data[:12] ciphertext = data[12:] try: plaintext = aesgcm.decrypt(nonce, ciphertext, None) except Exception: raise ValueError("解密失败,密钥无效或已被篡改") try: return json.loads(plaintext.decode("utf-8")) except Exception: raise ValueError("许可证数据解析失败") def validate_key(key: str): machine_code = get_machine_code() license_info = decrypt_key(key) if license_info.get("machine_code") != machine_code: raise ValueError("机器码不匹配,该密钥无法在本机使用") expire_str = license_info.get("expire_time") if not expire_str: raise ValueError("许可证信息不完整") expire_time = datetime.fromisoformat(expire_str) if datetime.now() > expire_time: raise ValueError("许可证已过期") return license_info def save_license(license_info: dict): with open(LICENSE_FILE, "w", encoding="utf-8") as f: json.dump(license_info, f, indent=2, ensure_ascii=False) def load_license(): if not os.path.exists(LICENSE_FILE): return None try: with open(LICENSE_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception: return None def verify_license(root): """ 直接在传入的 root 窗口上显示验证界面。 验证通过返回 True,关闭窗口返回 False。 """ import tkinter as tk from tkinter import messagebox # 1. 先检查本地缓存,如果有效直接通过 cached = load_license() if cached: try: if cached.get("machine_code") != get_machine_code(): raise ValueError("机器码不匹配") if datetime.now() > datetime.fromisoformat(cached.get("expire_time")): raise ValueError("许可证已过期") return True except Exception: if os.path.exists(LICENSE_FILE): os.remove(LICENSE_FILE) # 2. 没有缓存或缓存无效,显示验证界面 machine_code = get_machine_code() # 清空 root 里所有内容(防止有残留) for widget in root.winfo_children(): widget.destroy() # 设置验证窗口样式 root.title("软件激活") root.geometry("420x280") root.resizable(False, False) # 窗口居中 root.update_idletasks() w, h = 420, 280 x = (root.winfo_screenwidth() - w) // 2 y = (root.winfo_screenheight() - h) // 2 root.geometry(f"{w}x{h}+{x}+{y}") # 机器码(可复制) tk.Label(root, text="您的机器码(可复制):", font=("微软雅黑", 10)).pack(pady=(15, 5)) mc_entry = tk.Entry(root, font=("Consolas", 11), justify="center", width=50) mc_entry.insert(0, machine_code) mc_entry.config(state="readonly") mc_entry.pack(padx=20, pady=5) tk.Label(root, text="请将机器码发给管理员获取密钥", font=("微软雅黑", 9), fg="gray").pack(pady=5) # 密钥输入 tk.Label(root, text="许可证密钥:", font=("微软雅黑", 10)).pack(pady=(10, 5)) key_entry = tk.Entry(root, font=("Consolas", 11), show="*", width=50) key_entry.pack(padx=20, pady=5) key_entry.focus() error_label = tk.Label(root, text="", font=("微软雅黑", 9), fg="red") error_label.pack(pady=5) # 结果标志 result = [False] def on_verify(): key = key_entry.get().strip() if not key: error_label.config(text="密钥不能为空") return try: info = validate_key(key) save_license(info) expire = datetime.fromisoformat(info["expire_time"]) days_left = (expire - datetime.now()).days messagebox.showinfo( "激活成功", f"✓ 验证通过!\n有效期至:{expire.strftime('%Y-%m-%d %H:%M:%S')}\n剩余天数:{days_left} 天" ) result[0] = True root.quit() # 退出事件循环,不销毁窗口 except Exception as e: error_label.config(text=str(e)) key_entry.delete(0, tk.END) key_entry.focus() def on_close(): result[0] = False root.quit() # 按钮 btn_frame = tk.Frame(root) btn_frame.pack(pady=15) tk.Button(btn_frame, text="确认激活", command=on_verify, width=12, font=("微软雅黑", 10)).pack(side=tk.LEFT, padx=10) tk.Button(btn_frame, text="关闭", command=on_close, width=12, font=("微软雅黑", 10)).pack(side=tk.LEFT, padx=10) # 回车确认,X 按钮关闭 root.bind("", lambda e: on_verify()) root.protocol("WM_DELETE_WINDOW", on_close) # 显示窗口并阻塞等待用户操作 root.deiconify() root.mainloop() # 清理验证界面,把 root 交还给主程序 for widget in root.winfo_children(): widget.destroy() return result[0]