""" Python软件授权验证器 - 现代化GUI版本 集成了CustomTkinter实现的Material Design风格界面 """ import os import json import time import hashlib import requests from datetime import datetime, timedelta from typing import Optional, Tuple, Dict, Any import threading # 尝试导入CustomTkinter,如果失败则使用标准tkinter try: import customtkinter as ctk CTK_AVAILABLE = True ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue") # 设置主题和颜色 # ctk.set_appearance_mode("dark") # 可选: "light", "dark", "system" # ctk.set_default_color_theme("blue") # 可选: "blue", "green", "dark-blue" except ImportError: CTK_AVAILABLE = False import tkinter as tk from tkinter import simpledialog, messagebox # ===== 原有的类保持不变 ===== class MachineCodeGenerator: """独立版本的机器码生成器""" @staticmethod def generate() -> str: """生成32位机器码""" import platform import uuid hw_info = [] try: system = platform.system().lower() try: system_uuid = str(uuid.getnode()) if system_uuid and system_uuid != '0': hw_info.append(system_uuid) except: pass try: hostname = platform.node() if hostname: hw_info.append(hostname) except: pass try: system_info = f"{system}_{platform.release()}_{platform.machine()}" hw_info.append(system_info) except: pass try: python_version = platform.python_version() hw_info.append(python_version) except: pass except Exception as e: print(f"获取硬件信息时出错: {e}") if not hw_info: hw_info = [str(uuid.uuid4()), str(uuid.uuid4())] combined_info = '|'.join(hw_info) hash_obj = hashlib.sha256(combined_info.encode('utf-8')) machine_code = hash_obj.hexdigest()[:32].upper() return machine_code class SimpleCrypto: """简单的加密解密工具""" @staticmethod def generate_hash(data: str, salt: str = "") -> str: """生成哈希值""" combined = f"{data}{salt}".encode('utf-8') return hashlib.sha256(combined).hexdigest() @staticmethod def generate_signature(data: str, secret_key: str) -> str: """生成签名""" combined = f"{data}{secret_key}".encode('utf-8') return hashlib.sha256(combined).hexdigest() class AuthCache: """授权信息缓存管理""" def __init__(self, cache_file: str = ".auth_cache"): self.cache_file = cache_file self.cache_data = self._load_cache() def _load_cache(self) -> Dict[str, Any]: """加载缓存""" try: if os.path.exists(self.cache_file): with open(self.cache_file, 'r', encoding='utf-8') as f: return json.load(f) except Exception: pass return {} def _save_cache(self): """保存缓存""" try: with open(self.cache_file, 'w', encoding='utf-8') as f: json.dump(self.cache_data, f, ensure_ascii=False, indent=2) except Exception: pass def get_auth_info(self, software_id: str) -> Optional[Dict[str, Any]]: """获取授权信息""" key = f"auth_{software_id}" return self.cache_data.get(key) def set_auth_info(self, software_id: str, auth_info: Dict[str, Any]): """设置授权信息""" key = f"auth_{software_id}" self.cache_data[key] = auth_info self._save_cache() def clear_cache(self, software_id = None): """清除缓存""" if software_id: key = f"auth_{software_id}" self.cache_data.pop(key, None) else: self.cache_data.clear() self._save_cache() # ===== 现代化GUI组件 ===== class ModernAuthDialog: """现代化授权验证对话框""" def __init__(self, software_id: str, machine_code: str, on_verify_callback, parent=None): """初始化对话框""" self.software_id = software_id self.machine_code = machine_code self.on_verify_callback = on_verify_callback self.result = None self.license_key = None if CTK_AVAILABLE: self.window = ctk.CTk() else: self.window = tk.Tk() self._setup_window() self._create_widgets() def _setup_window(self): """设置窗口属性""" self.window.title("软件授权验证") if CTK_AVAILABLE: self.window.geometry("400x400") else: self.window.geometry("350x400") self.window.resizable(False, False) # 窗口居中 self.window.update_idletasks() width = self.window.winfo_width() height = self.window.winfo_height() x = (self.window.winfo_screenwidth() // 2) - (width // 2) y = (self.window.winfo_screenheight() // 2) - (height // 2) self.window.geometry(f'{width}x{height}+{x}+{y}') def _create_widgets(self): """创建界面组件""" if CTK_AVAILABLE: self._create_modern_widgets() else: self._create_classic_widgets() def _create_modern_widgets(self): """创建现代化界面(CustomTkinter)""" main_frame = ctk.CTkFrame(self.window, fg_color="transparent") main_frame.pack(fill="both", expand=True, padx=30, pady=30) # Logo区域 logo_frame = ctk.CTkFrame(main_frame, fg_color="transparent") logo_frame.pack(fill="x", pady=(0, 20)) ctk.CTkLabel( logo_frame, text="🔐", font=ctk.CTkFont(size=60) ).pack(pady=(0, 10)) ctk.CTkLabel( logo_frame, text="软件授权验证", font=ctk.CTkFont(size=28, weight="bold") ).pack() ctk.CTkLabel( logo_frame, text="请输入您的授权卡密以继续使用", font=ctk.CTkFont(size=13), text_color="gray60" ).pack(pady=(5, 0)) # 信息卡片 info_frame = ctk.CTkFrame(main_frame) info_frame.pack(fill="x", pady=20) # 软件ID software_id_frame = ctk.CTkFrame(info_frame, fg_color="transparent") software_id_frame.pack(fill="x", padx=20, pady=(15, 8)) ctk.CTkLabel( software_id_frame, text="软件ID", font=ctk.CTkFont(size=12, weight="bold"), text_color="gray70" ).pack(anchor="w") ctk.CTkLabel( software_id_frame, text=self.software_id, font=ctk.CTkFont(size=14), text_color="white" ).pack(anchor="w", pady=(5, 0)) ctk.CTkFrame(info_frame, height=1, fg_color="gray30").pack(fill="x", padx=20, pady=8) # 机器码 machine_code_frame = ctk.CTkFrame(info_frame, fg_color="transparent") machine_code_frame.pack(fill="x", padx=20, pady=(8, 15)) ctk.CTkLabel( machine_code_frame, text="机器码", font=ctk.CTkFont(size=12, weight="bold"), text_color="gray70" ).pack(anchor="w") machine_code_display = f"{self.machine_code[:8]}...{self.machine_code[-8:]}" ctk.CTkLabel( machine_code_frame, text=machine_code_display, font=ctk.CTkFont(size=14, family="Courier"), text_color="white" ).pack(anchor="w", pady=(5, 0)) # 输入区域 input_frame = ctk.CTkFrame(main_frame, fg_color="transparent") input_frame.pack(fill="x", pady=20) ctk.CTkLabel( input_frame, text="授权卡密", font=ctk.CTkFont(size=14, weight="bold"), anchor="w" ).pack(fill="x", pady=(0, 10)) self.license_entry = ctk.CTkEntry( input_frame, height=45, font=ctk.CTkFont(size=14), placeholder_text="请输入您的授权卡密", border_width=2 ) self.license_entry.pack(fill="x") self.license_entry.bind("", lambda e: self._verify_license()) ctk.CTkLabel( input_frame, text="💡 试用卡密以 'TRIAL_' 开头", font=ctk.CTkFont(size=11), text_color="gray60" ).pack(anchor="w", pady=(8, 0)) # 状态区域 self.status_frame = ctk.CTkFrame(main_frame, fg_color="transparent") self.status_frame.pack(fill="x", pady=(10, 20)) self.status_label = ctk.CTkLabel( self.status_frame, text="", font=ctk.CTkFont(size=12), text_color="gray60", wraplength=400 ) self.status_label.pack() # 按钮区域 button_frame = ctk.CTkFrame(main_frame, fg_color="transparent") button_frame.pack(fill="x", pady=(10, 0)) self.verify_button = ctk.CTkButton( button_frame, text="验证授权", height=45, font=ctk.CTkFont(size=15, weight="bold"), command=self._verify_license, corner_radius=10 ) self.verify_button.pack(fill="x", pady=(0, 10)) self.cancel_button = ctk.CTkButton( button_frame, text="取消", height=40, font=ctk.CTkFont(size=14), command=self._cancel, fg_color="gray30", hover_color="gray40", corner_radius=10 ) self.cancel_button.pack(fill="x") self.license_entry.focus() def _create_classic_widgets(self): """创建经典界面(标准Tkinter)""" main_frame = tk.Frame(self.window, bg="white") main_frame.pack(fill="both", expand=True, padx=20, pady=20) # 标题 tk.Label( main_frame, text="🔐 软件授权验证", font=("Arial", 20, "bold"), bg="white" ).pack(pady=(10, 20)) # 信息框 info_frame = tk.LabelFrame(main_frame, text="授权信息", font=("Arial", 10), bg="white") info_frame.pack(fill="x", pady=10) tk.Label(info_frame, text=f"软件ID: {self.software_id}", bg="white").pack(anchor="w", padx=10, pady=5) tk.Label(info_frame, text=f"机器码: {self.machine_code[:16]}...", bg="white").pack(anchor="w", padx=10, pady=5) # 输入框 input_frame = tk.Frame(main_frame, bg="white") input_frame.pack(fill="x", pady=20) tk.Label(input_frame, text="请输入授权卡密:", font=("Arial", 11), bg="white").pack(anchor="w") self.license_entry = tk.Entry(input_frame, font=("Arial", 12), width=40) self.license_entry.pack(fill="x", pady=10) self.license_entry.bind("", lambda e: self._verify_license()) # 状态标签 self.status_label = tk.Label(main_frame, text="", font=("Arial", 10), bg="white", fg="red") self.status_label.pack(pady=10) # 按钮 button_frame = tk.Frame(main_frame, bg="white") button_frame.pack(fill="x", pady=10) self.verify_button = tk.Button( button_frame, text="验证授权", font=("Arial", 11, "bold"), bg="#4CAF50", fg="white", command=self._verify_license, width=15, height=2 ) self.verify_button.pack(side="left", padx=5) self.cancel_button = tk.Button( button_frame, text="取消", font=("Arial", 11), command=self._cancel, width=15, height=2 ) self.cancel_button.pack(side="left", padx=5) self.license_entry.focus() def _show_status(self, message: str, is_error: bool = False): """显示状态消息""" if CTK_AVAILABLE: color = "#ff5555" if is_error else "#50fa7b" icon = "❌" if is_error else "✓" self.status_label.configure(text=f"{icon} {message}", text_color=color) else: color = "red" if is_error else "green" self.status_label.configure(text=message, fg=color) def _verify_license(self): """验证卡密""" license_key = self.license_entry.get().strip() if not license_key: self._show_status("请输入授权卡密", is_error=True) return self.license_key = license_key # 禁用控件 if CTK_AVAILABLE: self.verify_button.configure(state="disabled", text="验证中...") self.cancel_button.configure(state="disabled") self.license_entry.configure(state="disabled") else: self.verify_button.configure(state="disabled", text="验证中...") self.cancel_button.configure(state="disabled") self.license_entry.configure(state="disabled") # 在后台线程执行验证 def verify_thread(): success, message, auth_info = self.on_verify_callback(license_key) self.window.after(0, lambda: self._on_verify_complete(success, message, auth_info)) thread = threading.Thread(target=verify_thread, daemon=True) thread.start() def _on_verify_complete(self, success: bool, message: str, auth_info: Any): """验证完成""" # 恢复控件 if CTK_AVAILABLE: self.verify_button.configure(state="normal", text="验证授权") self.cancel_button.configure(state="normal") self.license_entry.configure(state="normal") else: self.verify_button.configure(state="normal", text="验证授权") self.cancel_button.configure(state="normal") self.license_entry.configure(state="normal") if success: self._show_status("验证成功!", is_error=False) self.result = (True, message, auth_info) self.window.after(1000, self.window.destroy) else: self._show_status(message, is_error=True) self.license_entry.delete(0, "end" if not CTK_AVAILABLE else "end") self.license_entry.focus() def _cancel(self): """取消""" self.result = (False, "用户取消", None) self.window.destroy() def show(self): """显示对话框""" self.window.mainloop() return self.result if self.result else (False, "用户取消", None) class ModernMessageBox: """现代化消息框""" @staticmethod def show_info(title: str, message: str): """显示信息""" if CTK_AVAILABLE: ModernMessageBox._show_ctk_message(title, message, is_error=False) else: messagebox.showinfo(title, message) @staticmethod def show_error(title: str, message: str): """显示错误""" if CTK_AVAILABLE: ModernMessageBox._show_ctk_message(title, message, is_error=True) else: messagebox.showerror(title, message) @staticmethod def _show_ctk_message(title: str, message: str, is_error: bool): """显示CTK消息框""" dialog = ctk.CTk() dialog.title(title) dialog.geometry("400x250") dialog.resizable(False, False) # 居中 dialog.update_idletasks() x = (dialog.winfo_screenwidth() // 2) - 200 y = (dialog.winfo_screenheight() // 2) - 125 dialog.geometry(f'400x250+{x}+{y}') main_frame = ctk.CTkFrame(dialog, fg_color="transparent") main_frame.pack(fill="both", expand=True, padx=30, pady=30) # 图标 icon = "❌" if is_error else "✓" color = "#ff5555" if is_error else "#50fa7b" ctk.CTkLabel( main_frame, text=icon, font=ctk.CTkFont(size=50), text_color=color ).pack(pady=(0, 15)) ctk.CTkLabel( main_frame, text=title, font=ctk.CTkFont(size=20, weight="bold") ).pack(pady=(0, 10)) ctk.CTkLabel( main_frame, text=message, font=ctk.CTkFont(size=13), text_color="gray70", wraplength=340 ).pack(pady=(0, 20)) ctk.CTkButton( main_frame, text="确定", height=40, font=ctk.CTkFont(size=14), command=dialog.destroy, corner_radius=10, fg_color="gray30" if is_error else None, hover_color="gray40" if is_error else None ).pack(fill="x") dialog.mainloop() # ===== AuthValidator类(集成GUI)===== class AuthValidator: """授权验证器主类""" def __init__(self, software_id: str, api_url: str = "http://localhost:5000/api/v1", secret_key: str = "taiyi1224", cache_days: int = 7, timeout: int = 3, gui_mode: bool = True): # 默认启用GUI """初始化验证器""" self.software_id = software_id self.api_url = api_url.rstrip('/') self.secret_key = secret_key self.cache_days = cache_days self.timeout = timeout self.gui_mode = gui_mode self.machine_generator = MachineCodeGenerator() self.cache = AuthCache() self.machine_code = self._get_or_generate_machine_code() self.failed_attempts = 0 self.last_attempt_time = None self.locked_until = None def _get_or_generate_machine_code(self) -> str: """获取或生成机器码""" cache_file = ".machine_code" try: if os.path.exists(cache_file): with open(cache_file, 'r') as f: cached_code = f.read().strip() if len(cached_code) == 32: return cached_code except Exception: pass machine_code = self.machine_generator.generate() try: with open(cache_file, 'w') as f: f.write(machine_code) except Exception: pass return machine_code def _validate_license_format(self, license_key: str) -> bool: """验证卡密格式""" if not license_key: return False license_key = license_key.strip().replace(' ', '').replace('\t', '').upper() if len(license_key) < 16 or len(license_key) > 32: return False import re pattern = r'^[A-Z0-9_]+$' return bool(re.match(pattern, license_key)) def _online_verify(self, license_key: str) -> Tuple[bool, str, Optional[Dict[str, Any]]]: """在线验证""" try: url = f"{self.api_url}/auth/verify" timestamp = int(datetime.utcnow().timestamp()) data = { "software_id": self.software_id, "license_key": license_key, "machine_code": self.machine_code, "timestamp": timestamp } signature_data = f"{data['software_id']}{data['license_key']}{data['machine_code']}{data['timestamp']}" signature = SimpleCrypto.generate_signature(signature_data, self.secret_key) data["signature"] = signature response = requests.post( url, json=data, timeout=self.timeout, headers={'Content-Type': 'application/json'} ) if response.status_code == 200: result = response.json() if result.get('success'): auth_info = result.get('data', {}) return True, "验证成功", auth_info else: return False, result.get('message', '验证失败'), None else: return False, f"网络请求失败: {response.status_code}", None except requests.exceptions.Timeout: return False, "网络超时,请检查网络连接", None except requests.exceptions.ConnectionError: return False, "无法连接到服务器", None except Exception as e: return False, f"验证过程出错: {str(e)}", None def _offline_verify(self) -> Tuple[bool, str]: """离线验证""" auth_info = self.cache.get_auth_info(self.software_id) if not auth_info: return False, "未找到有效的离线授权信息,请联网验证" cache_time = datetime.fromisoformat(auth_info.get('cache_time', '')) if datetime.utcnow() - cache_time > timedelta(days=self.cache_days): return False, f"离线授权已过期(超过{self.cache_days}天),请联网验证" if auth_info.get('machine_code') != self.machine_code: return False, "设备信息不匹配,请重新验证" expire_time = auth_info.get('expire_time') if expire_time: expire_datetime = datetime.fromisoformat(expire_time) if datetime.utcnow() > expire_datetime: return False, "授权已过期,请重新验证" return True, "离线验证成功" def _cache_auth_info(self, auth_info: Dict[str, Any]): """缓存授权信息""" auth_info['cache_time'] = datetime.utcnow().isoformat() self.cache.set_auth_info(self.software_id, auth_info) def validate(self) -> bool: """执行验证流程""" # 首先尝试离线验证 offline_success, offline_message = self._offline_verify() if offline_success: return True # 离线验证失败,显示GUI输入卡密 if self.gui_mode: dialog = ModernAuthDialog( software_id=self.software_id, machine_code=self.machine_code, on_verify_callback=self._online_verify ) success, message, auth_info = dialog.show() if success and auth_info: self._cache_auth_info(auth_info) force_update = auth_info.get('force_update', False) download_url = auth_info.get('download_url') new_version = auth_info.get('new_version') if force_update and download_url: ModernMessageBox.show_error( "需要更新", f"发现新版本 {new_version}\n请下载更新后重新启动程序" ) try: import webbrowser webbrowser.open(download_url) except: pass return False expire_time = auth_info.get('expire_time', '永久') ModernMessageBox.show_info( "验证成功", f"授权验证成功!\n\n有效期至: {expire_time}" ) return True else: return False else: # 命令行模式(保留原有逻辑) print("命令行模式暂未实现,请使用gui_mode=True") return False def clear_cache(self): """清除本地缓存""" self.cache.clear_cache(self.software_id) try: if os.path.exists(".machine_code"): os.remove(".machine_code") except Exception: pass # ===== 便捷函数 ===== def validate_license(software_id: str, **kwargs) -> bool: """便捷的验证函数""" validator = AuthValidator(software_id, **kwargs) return validator.validate() def get_machine_code() -> str: """获取当前机器码""" return MachineCodeGenerator.generate() # ===== 测试代码 ===== if __name__ == "__main__": # 测试验证器 validator = AuthValidator( software_id="TEST_SOFTWARE_001", api_url="http://localhost:5000/api/v1", gui_mode=True ) if validator.validate(): print("✓ 授权验证成功!") else: print("✗ 授权验证失败!")