""" 安全的验证器 - 通过API验证,不包含数据库密码 作者:太一 """ import os import sys import json import hashlib import tempfile import subprocess import tkinter as tk from tkinter import messagebox import threading import time import hmac import base64 from datetime import datetime, timedelta from cryptography.fernet import Fernet import requests # ========== 配置区(这些可以公开) ========== API_BASE_URL = "https://your-server.com/api" # 替换为你的服务器地址 API_KEY = "your-secure-api-key-here" # 从环境变量读取更安全 SOFTWARE_NAME = "SOFTWARE_NAME_PLACEHOLDER" # 打包时替换 # 本地缓存配置 CACHE_FILE = os.path.join(tempfile.gettempdir(), '.lic_cache') CACHE_VALIDITY_HOURS = 24 # 缓存有效期 # ========== 机器码生成 ========== def get_machine_code(): """生成机器码(简化且稳定的版本)""" import platform import uuid try: if platform.system() == 'Windows': # 优先使用主板序列号 try: import subprocess result = subprocess.check_output( 'wmic baseboard get serialnumber', shell=True, stderr=subprocess.DEVNULL, timeout=5 ).decode().strip() lines = [line.strip() for line in result.split('\n') if line.strip()] if len(lines) > 1 and lines[1] not in ['To Be Filled By O.E.M.', '']: return hashlib.md5(lines[1].encode()).hexdigest()[:16].upper() except: pass # 备用方案:使用MAC地址 mac = uuid.getnode() mac_str = ':'.join(['{:02x}'.format((mac >> i) & 0xff) for i in range(0, 48, 8)][::-1]) return hashlib.md5(mac_str.encode()).hexdigest()[:16].upper() except Exception as e: # 最终备用 fallback = f"{platform.node()}{uuid.getnode()}" return hashlib.md5(fallback.encode()).hexdigest()[:16].upper() # ========== 加密缓存 ========== def _get_cache_key(): """获取缓存加密密钥(基于机器码)""" machine_key = get_machine_code().encode()[:32].ljust(32, b'0') return base64.urlsafe_b64encode(machine_key) def save_cache(license_key, token, expires_at): """保存验证缓存""" try: data = { 'license_key': license_key, 'token': token, 'expires_at': expires_at, 'machine_code': get_machine_code() } fernet = Fernet(_get_cache_key()) encrypted = fernet.encrypt(json.dumps(data).encode()) with open(CACHE_FILE, 'wb') as f: f.write(encrypted) return True except Exception as e: print(f"保存缓存失败: {e}") return False def load_cache(): """加载验证缓存""" try: if not os.path.exists(CACHE_FILE): return None with open(CACHE_FILE, 'rb') as f: encrypted = f.read() fernet = Fernet(_get_cache_key()) decrypted = fernet.decrypt(encrypted) data = json.loads(decrypted.decode()) # 验证机器码 if data.get('machine_code') != get_machine_code(): return None # 验证过期时间 expires_at = datetime.fromisoformat(data.get('expires_at')) if datetime.now() > expires_at: return None return data except Exception as e: print(f"加载缓存失败: {e}") return None def clear_cache(): """清除缓存""" try: if os.path.exists(CACHE_FILE): os.remove(CACHE_FILE) except: pass # ========== API通信 ========== def create_signature(license_key, machine_code, software_name): """创建请求签名""" message = f"{license_key}{machine_code}{software_name}" return hmac.new(API_KEY.encode(), message.encode(), hashlib.sha256).hexdigest() def validate_license_online(license_key, machine_code): """在线验证卡密""" try: signature = create_signature(license_key, machine_code, SOFTWARE_NAME) response = requests.post( f"{API_BASE_URL}/validate", json={ 'license_key': license_key, 'machine_code': machine_code, 'software_name': SOFTWARE_NAME, 'signature': signature }, headers={'X-API-Key': API_KEY}, timeout=10 ) if response.status_code == 200: data = response.json() if data.get('success'): # 保存缓存 save_cache( license_key, data.get('token', ''), data.get('expires_at', (datetime.now() + timedelta(hours=24)).isoformat()) ) return True, data.get('message', '验证成功') else: return False, data.get('message', '验证失败') else: return False, f"服务器错误: {response.status_code}" except requests.exceptions.ConnectionError: return False, "无法连接到服务器,请检查网络" except requests.exceptions.Timeout: return False, "服务器响应超时" except Exception as e: return False, f"验证过程出错: {str(e)}" def validate_license_offline(cache_data): """离线验证(使用缓存)""" try: # 验证token(可选:发送到服务器验证) token = cache_data.get('token', '') if not token: return False, "缓存数据不完整" # 这里可以添加本地token验证逻辑 # 或者在有网络时向服务器验证token return True, "离线验证成功(使用缓存)" except Exception as e: return False, f"离线验证失败: {str(e)}" # ========== 资源解密 ========== # ENCRYPTED_RESOURCE_PLACEHOLDER ENCRYPTED_RESOURCE = None def decrypt_resource(): """解密嵌入的资源""" try: if not ENCRYPTED_RESOURCE: return None, "未找到嵌入资源" from cryptography.fernet import Fernet import base64 # 重建完整密钥 # key_hint包含前半部分,key_part2包含后半部分 key_part1_bytes = base64.b64decode(ENCRYPTED_RESOURCE['key_hint'].encode()) key_part2_bytes = base64.b64decode(ENCRYPTED_RESOURCE['key_part2'].encode()) # 合并密钥 full_key = key_part1_bytes + key_part2_bytes # 解密 fernet = Fernet(full_key) encrypted_data = base64.b64decode(ENCRYPTED_RESOURCE['data'].encode()) decrypted = fernet.decrypt(encrypted_data) # 验证哈希 if hashlib.sha256(decrypted).hexdigest() != ENCRYPTED_RESOURCE['hash']: return None, "文件完整性校验失败" return decrypted, "解密成功" except Exception as e: return None, f"解密失败: {str(e)}" def extract_and_run(): """提取并运行原始程序""" try: exe_content, msg = decrypt_resource() if not exe_content: return False, msg # 写入临时文件 temp_path = os.path.join(tempfile.gettempdir(), f"app_{os.getpid()}.exe") with open(temp_path, 'wb') as f: f.write(exe_content) # 设置权限 if hasattr(os, 'chmod'): os.chmod(temp_path, 0o755) # 启动程序 if os.name == 'nt': subprocess.Popen([temp_path], creationflags=subprocess.CREATE_NEW_CONSOLE) else: subprocess.Popen([temp_path]) time.sleep(2) # 等待程序启动 return True, "程序启动成功" except Exception as e: return False, f"启动失败: {str(e)}" # ========== GUI ========== def show_activation_dialog(): """显示激活对话框""" def on_activate(): license_key = entry_key.get().strip() if not license_key: messagebox.showerror("错误", "请输入激活码") return btn_activate.config(state=tk.DISABLED, text="验证中...") status_label.config(text="正在验证激活码...", fg="blue") root.update() def validate_thread(): machine_code = get_machine_code() success, msg = validate_license_online(license_key, machine_code) root.after(0, lambda: handle_result(success, msg)) threading.Thread(target=validate_thread, daemon=True).start() def handle_result(success, msg): if success: messagebox.showinfo("成功", "激活成功!正在启动程序...") root.destroy() # 启动程序 ok, err = extract_and_run() if ok: sys.exit(0) else: messagebox.showerror("错误", err) sys.exit(1) else: messagebox.showerror("失败", msg) btn_activate.config(state=tk.NORMAL, text="验证并启动") status_label.config(text="", fg="black") machine_code = get_machine_code() root = tk.Tk() root.title("软件激活验证") root.geometry("500x400") root.resizable(False, False) # 居中 root.update_idletasks() x = (root.winfo_screenwidth() - root.winfo_width()) // 2 y = (root.winfo_screenheight() - root.winfo_height()) // 2 root.geometry(f"500x400+{x}+{y}") # 标题 tk.Label(root, text="🔐 软件许可证验证", font=("Microsoft YaHei", 16, "bold")).pack(pady=20) # 机器码 frame_machine = tk.Frame(root) frame_machine.pack(fill=tk.X, padx=30, pady=10) tk.Label(frame_machine, text="机器码:", font=("Microsoft YaHei", 10, "bold")).pack(anchor=tk.W) entry_machine = tk.Entry(frame_machine, state="readonly", font=("Consolas", 10)) entry_machine.insert(0, machine_code) entry_machine.pack(fill=tk.X, pady=5) def copy_machine_code(): root.clipboard_clear() root.clipboard_append(machine_code) messagebox.showinfo("成功", "机器码已复制") tk.Button(frame_machine, text="复制机器码", command=copy_machine_code).pack(anchor=tk.E) # 激活码 frame_key = tk.Frame(root) frame_key.pack(fill=tk.X, padx=30, pady=10) tk.Label(frame_key, text="激活码:", font=("Microsoft YaHei", 10, "bold")).pack(anchor=tk.W) entry_key = tk.Entry(frame_key, font=("Consolas", 12)) entry_key.pack(fill=tk.X, pady=5) entry_key.focus() # 状态 status_label = tk.Label(root, text="", font=("Microsoft YaHei", 9)) status_label.pack(pady=10) # 按钮 frame_buttons = tk.Frame(root) frame_buttons.pack(pady=20) btn_activate = tk.Button(frame_buttons, text="验证并启动", command=on_activate, bg="#27ae60", fg="white", font=("Microsoft YaHei", 11, "bold"), padx=20, pady=8) btn_activate.pack(side=tk.LEFT, padx=10) tk.Button(frame_buttons, text="退出", command=sys.exit, bg="#e74c3c", fg="white", font=("Microsoft YaHei", 11, "bold"), padx=20, pady=8).pack(side=tk.LEFT, padx=10) root.bind('', lambda e: on_activate()) root.protocol("WM_DELETE_WINDOW", sys.exit) root.mainloop() # ========== 主函数 ========== def main(): """主函数""" # 1. 尝试加载缓存 cache_data = load_cache() if cache_data: print("找到有效缓存,尝试离线验证...") success, msg = validate_license_offline(cache_data) if success: print(f"离线验证成功: {msg}") ok, err = extract_and_run() if ok: sys.exit(0) else: print(f"启动失败: {err}") clear_cache() else: print(f"离线验证失败: {msg}") clear_cache() # 2. 需要在线验证 show_activation_dialog() if __name__ == '__main__': main()