独立授权模块 --可以为你的程序或者工具加上一把锁

# 文件名: 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()
View Code

二、如何在 其他程序中植入 注册授权?

步骤 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 工具中植入这个注册系统,实现 “未注册无法使用” 的商业闭环。

posted @ 2025-11-10 16:44  *感悟人生*  阅读(7)  评论(0)    收藏  举报