独立授权模块 --可以为你的程序或者工具加上一把锁
# 文件名: license_manager.py # 功能: 软件注册授权核心模块(支持离线激活、机器码绑定、过期控制) # 作者: 三松强哥(商业软件级封装) # 日期: 2025-11-10 # 适用: 可植入任何 Python 桌面软件(Tkinter / PyQt / wxPython / Electron 等) import hashlib import uuid import json import os from datetime import datetime, timedelta from cryptography.fernet import Fernet from typing import Optional import logging # ============================== # 日志配置(可替换为项目主日志系统) # ============================== logging.basicConfig(level=logging.INFO) log = logging.getLogger("LicenseManager") class LicenseManager: """ 注册授权核心类 ----------------- 功能: 1. 生成机器码(唯一标识本机) 2. 生成离线激活码(管理员用) 3. 验证激活码并写入本地 4. 检查授权状态(是否激活、是否过期) 5. 支持加密存储,防止篡改 """ # ------------------------------- # 配置文件名(建议放在软件同目录) # ------------------------------- LICENSE_FILE = "license.dat" # 存储激活码(加密) KEY_FILE = "license.key" # 加密密钥(本地生成,一机一钥) def __init__(self): self.fernet = self._load_or_create_fernet() self.machine_id = self._get_machine_id() # ============================== # 1. 获取本机唯一标识(机器码) # ============================== def _get_machine_id(self) -> str: """ 获取本机硬件唯一标识 - 使用 MAC 地址 + 磁盘序列号(更稳定) - 返回 16 位 SHA256 哈希前缀 """ try: # 方法1:MAC 地址 mac = uuid.getnode() mac_str = str(mac) # 方法2:磁盘卷序列号(Windows 推荐) import ctypes vol = ctypes.windll.kernel32.GetVolumeInformationW( "C:\\", None, 0, None, None, None, None, 0 ) if vol: disk_sn = str(ctypes.windll.kernel32.GetVolumeInformationW.call_type) else: disk_sn = "" # 组合 + 哈希 raw = f"{mac_str}{disk_sn}{os.getlogin()}" return hashlib.sha256(raw.encode('utf-8')).hexdigest()[:16].upper() except Exception as e: log.warning(f"获取机器码失败,使用随机ID: {e}") return hashlib.sha256(str(uuid.uuid4()).encode()).hexdigest()[:16].upper() # ============================== # 2. 加载或生成加密密钥 # ============================== def _load_or_create_fernet(self): """ 加载本地密钥或生成新密钥 - 密钥存储在 license.key 文件中 - 一机一钥,防止复制授权 """ key_path = self.KEY_FILE if os.path.exists(key_path): try: key = open(key_path, 'rb').read() return Fernet(key) except Exception as e: log.error(f"密钥损坏,重新生成: {e}") # 生成新密钥 key = Fernet.generate_key() try: with open(key_path, 'wb') as f: f.write(key) # 隐藏文件(Windows) if os.name == 'nt': import ctypes ctypes.windll.kernel32.SetFileAttributesW(key_path, 2) # FILE_ATTRIBUTE_HIDDEN except Exception as e: log.error(f"保存密钥失败: {e}") return Fernet(key) # ============================== # 3. 生成激活码(管理员专用) # ============================== def generate_license_key(self, days: int = 365, user_id: str = "") -> str: """ 生成离线激活码(管理员运行此函数) :param days: 有效天数 :param user_id: 可选用户ID :return: 激活码字符串 """ expiry = (datetime.now() + timedelta(days=days)).isoformat(timespec='seconds') payload = { "mid": self.machine_id, # 机器码 "exp": expiry, # 过期时间 "uid": user_id or "user", # 用户ID "ver": "1.0" # 版本控制 } token = self.fernet.encrypt(json.dumps(payload, ensure_ascii=False).encode('utf-8')) return token.decode('utf-8') # ============================== # 4. 激活软件(用户输入激活码) # ============================== def activate(self, license_key: str) -> bool: """ 验证并激活 :param license_key: 用户输入的激活码 :return: True=激活成功 """ try: # 解密 data = json.loads(self.fernet.decrypt(license_key.strip().encode('utf-8')).decode('utf-8')) # 校验机器码 if data.get("mid") != self.machine_id: log.warning(f"机器码不匹配: {data.get('mid')} != {self.machine_id}") return False # 校验过期时间 exp = datetime.fromisoformat(data["exp"]) if datetime.now() > exp: log.warning(f"激活码已过期: {exp}") return False # 写入本地 with open(self.LICENSE_FILE, 'w', encoding='utf-8') as f: f.write(license_key.strip()) # 隐藏文件 if os.name == 'nt': import ctypes ctypes.windll.kernel32.SetFileAttributesW(self.LICENSE_FILE, 2) log.info(f"激活成功,有效至: {exp.strftime('%Y-%m-%d %H:%M')}") return True except Exception as e: log.error(f"激活失败: {e}") return False # ============================== # 5. 检查授权状态 # ============================== def is_activated(self) -> bool: """检查是否已激活且未过期""" if not os.path.exists(self.LICENSE_FILE): return False try: with open(self.LICENSE_FILE, 'r', encoding='utf-8') as f: key = f.read().strip() return self.activate(key) # 重新验证 except: return False # ============================== # 6. 获取剩余天数 # ============================== def get_remaining_days(self) -> Optional[int]: """返回剩余天数,None=未激活""" if not self.is_activated(): return None try: with open(self.LICENSE_FILE, 'r', encoding='utf-8') as f: key = f.read().strip() data = json.loads(self.fernet.decrypt(key.encode('utf-8')).decode('utf-8')) exp = datetime.fromisoformat(data["exp"]) days = (exp - datetime.now()).days return max(0, days) except: return None # ============================== # 7. 获取授权信息(用于显示) # ============================== def get_license_info(self) -> dict: """返回授权详细信息""" if not self.is_activated(): return {"status": "未激活"} try: with open(self.LICENSE_FILE, 'r', encoding='utf-8') as f: key = f.read().strip() data = json.loads(self.fernet.decrypt(key.encode('utf-8')).decode('utf-8')) exp = datetime.fromisoformat(data["exp"]) return { "status": "已激活", "machine_id": self.machine_id, "expiry": exp.strftime('%Y-%m-%d %H:%M'), "remaining_days": (exp - datetime.now()).days, "user_id": data.get("uid", "未知") } except: return {"status": "授权异常"} # ============================== # 8. 管理员工具:生成激活码(命令行) # ============================== @staticmethod def cli_generate(): """命令行生成激活码(管理员用)""" print("=== MidJourney 工具 激活码生成器 ===") mid = input("请输入用户机器码(16位): ").strip().upper() if len(mid) != 16: print("机器码必须是16位!") return days = input("授权天数(默认365): ").strip() days = int(days) if days.isdigit() else 365 # 临时创建实例(使用 mid) class TempLM: def __init__(self): self.machine_id = mid self.fernet = Fernet(Fernet.generate_key()) temp = TempLM() key = temp.generate_license_key(days=days) print("\n" + "="*50) print("激活码(复制给用户):") print(key) print("="*50) print(f"有效期: {days} 天") print("请将此码发给用户,在软件中激活。") # ============================== # 独立运行时:生成激活码工具 # ============================== if __name__ == "__main__": LicenseManager.cli_generate()
二、如何在 其他程序中植入 注册授权?
步骤 1:复制文件
将 license_manager.py 放入你的项目目录,例如:
MyTool/ ├── main.py ├── ui/ ├── core/ └── license_manager.py ← 复制这里
步骤 2:在主程序启动时 强制检查授权
# main.py 或 入口文件 import tkinter as tk from tkinter import messagebox from license_manager import LicenseManager def check_license_and_start(): lm = LicenseManager() # 检查是否已激活 if not lm.is_activated(): if not show_activation_dialog(): return False # 退出程序 else: info = lm.get_license_info() if info["remaining_days"] is not None and info["remaining_days"] <= 7: messagebox.showwarning("授权即将过期", f"还剩 {info['remaining_days']} 天") return True def show_activation_dialog(): """弹窗让用户输入激活码""" code = tk.simpledialog.askstring( "软件激活", "请输入激活码(联系管理员获取):\n\n" f"本机机器码:{LicenseManager().machine_id}\n" "(请提供此码给管理员生成激活码)", parent=root ) if code and LicenseManager().activate(code): messagebox.showinfo("成功", "激活成功!软件已解锁。") return True else: messagebox.showerror("失败", "激活码无效或已过期") return False # ========= 主程序入口 ========= if __name__ == "__main__": root = tk.Tk() root.withdraw() # 先隐藏主窗口 if check_license_and_start(): root.deiconify() # 显示主界面 # 启动你的主程序 from ui.main_window import MainApp app = MainApp(root) root.mainloop() else: root.destroy() # 未激活,退出
步骤 3:在界面中 显示授权状态
# 在你的主界面 __init__ 中 self.lm = LicenseManager() info = self.lm.get_license_info() status_label = tk.Label(self.root, text=f"授权:{info['status']}") status_label.pack(anchor='e', padx=10) if info.get("remaining_days") is not None: tk.Label(self.root, text=f"剩余:{info['remaining_days']} 天", fg="red").pack(anchor='e')
步骤 4:管理员生成激活码(独立运行)
python license_manager.py
三、关键优势(商业级)
| 特性 | 说明 |
|---|---|
| 一机一码 | 复制软件无效 |
| 离线激活 | 无需联网 |
| 防篡改 | 加密存储 + 隐藏文件 |
| 过期控制 | 自动失效 |
| 跨项目复用 | 复制即用 |
| 管理员友好 | 命令行生成工具 |
四、打包建议(PyInstaller)
pyinstaller --onefile --windowed --add-data "licens
--add-data 确保模块被打包进去。
现在你可以在任何 Python 工具中植入这个注册系统,实现 “未注册无法使用” 的商业闭环。

浙公网安备 33010602011771号