大家好,我是机灵鹤。

前几天有个粉丝问我说,他写了一个蛮有意思的小程序,准备在网上发布,但是又担心程序在不受控制的情况下传播。想问我有没有办法整一个授权码机制,只有输入授权码才能使用软件,授权码过期或者更换机器都需要重新授权。

真是蛮有意思又非常实用的一个小需求,以后如果想要分享自己的程序但又不希望自己的程序被随意传播的,都可以用得上。

这里给大家简单分享一下我的实现方法,感兴趣的同学可以学起来。

如果你有其它比较好的思路,也欢迎跟我一起交流。


一、总体思路

软件授权方案大概分成两个部分:程序本体注册机

当用户启动程序时,程序会检验本地的 授权文件 是否合法,若验证通过,则直接进入程序,若未找到授权文件或者授权文件校验失败,则进入重新授权流程。进入授权流程时,程序先扫描本机运行环境,生成 机器码 ,然后提示用户找管理员获取授权码;用户将机器码发送给管理员,管理员将机器码输入 注册机 中,生成与该机器码唯一绑定的 授权码 后,发送给用户;用户在程序中输入授权码,验证通过后正式进入程序,并在本地生成授权文件。

下面是我的软件授权方案的流程图。

授权流程图

以上便是我这套软件授权方案的总体思路,接下来,我会教大家如何用 python 来实现它。

二、实现过程

实现这套授权机制,我们需要解决以下几个小问题。

  1. 如何使授权码与机器唯一绑定,仅在本台机器上有效?
  2. 如何生成验证码,以及如何验证授权码是否有效?
  3. 如何保护自己的授权码不那么容易被人破解?

带着这些问题,我们继续往下看。

2.1 获取机器信息

要回答第一个问题,首先要搞明白,软件如何判断自己运行在哪一台机器上?

我们知道,每一台机器都会有一个唯一 Mac 地址,我们可以用它来作为机器的唯一标识。

此外为了保险,我们还可以获取机器的 CPU 序列号硬盘序列号主板序列号 等等数据,与 Mac 地址 共同作为一台机器的唯一标识。

windows 系统下,我们可以使用 wmi 的库来获取机器的硬件信息。

没有安装 wmi 库的,可以运行下面的命令行来安装。

pip install wmi

2.1.1 获取 CPU 序列号

import wmi

m_wmi = wmi.WMI()
cpu_info = m_wmi.Win32_Processor()
if len(cpu_info) > 0:
    serial_number = cpu_info[0].ProcessorId
    print(serial_number)

2.1.2 获取 MAC 地址

import wmi

m_wmi = wmi.WMI()
for network in m_wmi.Win32_NetworkAdapterConfiguration():
    mac_address = network.MacAddress
    if mac_address != None:
        print(mac_address)

2.1.3 获取硬盘序列号

import wmi

m_wmi = wmi.WMI()
disk_info = m_wmi.Win32_PhysicalMedia()
if len(disk_info) > 0:
    serial_number = disk_info[0].SerialNumber.strip()
    print(serial_number)

2.1.4 获取主板序列号

import wmi

m_wmi = wmi.WMI()
board_info = self.m_wmi.Win32_BaseBoard()
if len(board_info) > 0:
    board_id = board_info[0].SerialNumber.strip().strip('.')
    print(board_id)

2.2 生成机器码

使用上述方法,我们可以获取到机器的 CPU 序列号硬盘序列号主板序列号Mac 地址 信息,通过这些信息,我们便可以生成唯一标识一台机器的 机器码

理论上,我们将这些数据直接进行字符串拼接,便可作为机器码使用,但是实际上,这样做存在一些问题。

  1. 机器码过长,使用体验不太友好。
  2. 直接暴露机器的硬件数据,有信息安全隐患。

所以我们需要对机器的这些硬件数据进行一些处理,生成一个长度适中的,既可以作为机器的唯一标识,又不会暴露机器硬件数据的机器码。

# 获取机器的 Mac地址、CPU序列号、硬盘序列号、主板序列号
mac_address = get_mac_address()
cpu_serial = get_cpu_serial()
disk_serial = get_disk_serial()
board_serial = get_board_serial()

# 将机器的硬件数据字符串拼接
combine_str =  mac_address + cpu_serial + disk_serial + board_serial
combine_byte = combine_str.encode("utf-8")

# 进行 MD5 编码
machine_code = hashlib.md5(combine_byte).hexdigest()
print(machine_code.upper())

众所周知,MD5 是一种常用的不可逆的数据加密算法,我们用它对机器硬件数据进行加密,一方面它加密后的结果长度固定(32个字符),另一方面也可以在一定程度上保护数据安全。

2.3 授权验证逻辑

想要在离线的情况下实现 授权码机器码 唯一绑定,意味着授权码是由机器码经过一系列复杂的加密算法处理后得到的。

授权码验证的逻辑也比较简单,大概有三种思路:

  1. 将机器码使用相同的加密算法处理,得到的结果与验证码进行比较。
  2. 将验证码使用对应的解密算法处理,得到的结果与机器码进行比较。
  3. 使用特定的算法分别处理机器码和授权码,将得到的结果进行比较。

一般来讲,第一种思路实现起来会比较简单一些,因为它只需要写一套加密算法即可(软件验证部分跟授权码生成部分用的是同一套加密算法),而且无需考虑解密的问题,安全性会更高(因为可以使用不可逆的加密算法)

大概的验证逻辑如下:

# 获取机器码
machine_code = getMachineCode()
# 自己定义 Encrypted 函数进行加密处理
encrypt_code = Encrypted(machine_code.encode("utf-8"))

# 读取本地的授权文件
if os.path.exists("register.bin"):
    with open("register.bin", "r") as f:
        key_code = f.read()
        # 如果机器码经过加密后的值,等于授权码的值,则验证通过,否则验证失败
        if key_code == encrypt_code:
            print("验证通过")
        else:
            print("验证失败")
else:
    print("验证失败")

2.3 生成授权码

如何将机器码加密生成授权码,可以使用的加密算法其实五花八门,每个人都可以自己研究一套自己独有的加密方法,尤其是不需要考虑解密,不要求算法可逆的情况下,问题就更简单了。

比如:

  • 你可以提取机器码的奇数位或偶数位,或者自己定义的任意位置的字符出来,组合成授权码。
  • 可以将指定位置的字符进行交换,得到的字符串作为授权码。
  • 可以将其中的某些字符替换成其它字符,得到的字符串作为授权码。
  • 甚至可以直接再进行一次 MD5 加密,结果作为授权码。

总之,方法是非常多样化的,只要你生成的授权码可以正常使用,并且没那么容易让别人猜出你授权码生成的规律即可。

作为示例,我演示一下我 Demo 中的加密方法。

import base64
import hashlib
from pyDes import *

def Encrypted(self, code):
    # 使用 DES-CBC加密算法加密机器码
    Des_key = "posdvsgt"                 #自定义 Key,需八位
    Des_IV = "\x11\2\x2a\3\1\x27\2\0"      # 自定IV向量
    k = des(Des_key, CBC, Des_IV, pad=None, padmode=PAD_PKCS5)
    EncryptStr = k.encrypt(code)
    # 加密结果转 base64 编码
    base64_code = base64.b32encode(EncryptStr)
    # 编码结果使用 MD5 加密
    md5_code = hashlib.md5(base64_code).hexdigest().upper()
    return md5_code

通过上述加密算法,我们可以通过机器码生成授权码,并且通过一系列加密算法的组合加密,基本上很难观察出机器码和授权码之间的关系,更没办法推算出你具体用了什么加密方法。

需要说明的是,

不管使用什么算法,凡离线方式的加密必然是可以被破解的,只是破解的时间和成本问题,所以上述的加密只是提高了破解门槛,防小白不防大神。

想要万无一失,还得是在线授权验证。

2.5 打包测试

核心算法搞定以后,整理一下代码就可以打包测试了。


可以使用 pyinstaller 库来把代码打包成 .exe 程序。

没有安装 pyinstaller 库的,可以运行下面的命令行来安装。

pip install pyinstaller

使用 pyinstaller 库打包的命令如下:

pyinstaller -F xxx.py

打包完成以后,会在当前目录下的 dist 文件夹中,生成 xxx.exe 可执行程序。


首次启动程序时(无授权文件),会提示输入激活码,即授权码。

随便输入错误的授权码,会验证失败,提示重新输入。

image-20221110185635873

此时我们启动注册机,根据提示复制机器码 BD1F7DF8646CD3A101C3DA8610672ED1 到注册机中,生成激活码。

image-20221110190013275

复制并输入激活码,此时授权验证成功,程序可以正常使用,输出了 Hello World! 字样。

image-20221110190611432

后续再次启动程序时,由于已有授权文件,所以可以直接进入。

image-20221111100332284

2.6 源码分享

为了方便大家学习交流,我将代码整理打包上传,并附上了一个 Demo 程序。

关注公众号 机灵鹤 并回复文字 授权码 ,即可获取。

感兴趣的同学可以自行下载,大家一起交流学习。

四、改进措施

当然,作为 V1.0 版本的授权码机制,其实还是有很多值得改进的地方,比如:

  • 设置授权有效期,过期则需要重新授权
  • 授权方式改成在线,安全性更高的同时也可以进行更加精细化的授权管理。
  • 授权后台管理系统,更加方便地管理自己的软件授权。
  • ......

如果后续大家有需求,我也有精力的话,可以把它做的更加完善一些。


如果文章中有哪里没有讲明白,或者讲解有误的地方,欢迎在评论区批评指正,或者扫描下面的二维码,加我微信,大家一起学习交流,共同进步。

img

最后修改:2022 年 11 月 13 日 04 : 17 PM
如果觉得我的文章对你有用,请随意赞赏