Python工具的License授权机制

一、引题

我写了一个python脚本eda.py,内容如下。

def function():
    print('I am an EDA tool!')
function()

我决定把这个“牛逼”的脚本分享给别人使用。但是主要的障碍在于,我不希望别人随便传播这个脚本,不希望别人无限期使用这个脚本,不希望别人学会这几行代码的写法,于是我决定对它加上License限制。

二、生成License文件

工具License常在如下方面对工具的使用加以限制:

使用的MAC地址。(防止工具被随意拷贝使用)

使用期限。(过期失效)

       更加复杂的应用场景,还可以对License使用数量、用户列表、工具feature等因素加以限制。

       按照这个需求,我们要设计一个简化的License文件,它需要包含以下内容:

MAC :允许工具启动的机器的MAC地址。

Date :工具有效期。

Sign :签名,对以上明文内容的加密,用作内容核验。

        下面直接给出生成License文件的脚本

#!/usr/bin/env python3
from Crypto.Cipher import AES
from binascii import b2a_hex

def encrypt(content):

    # content length must be a multiple of 16.

    while len(content) % 16:

        content += ' '

    content = content.encode('utf-8')

    # Encrypt content.

    aes = AES.new(b'2024052020240520', AES.MODE_CBC, b'2024052020240520')

    encrypted_content = aes.encrypt(content)

    return(b2a_hex(encrypted_content))

def gen_license_file():

    license_file = './License.dat'

    with open(license_file, 'w') as LF:

        LF.write('MAC : 00:50:56:b1:01:e5\n')

        LF.write('Date : 20240520\n')

        sign = encrypt('00:50:56:b1:01:e5#20240520')

        LF.write('Sign : ' + str(sign.decode('utf-8')) + '\n')



if __name__ == '__main__':

    gen_license_file()

          我们利用这个脚本可以生成一个添加了限定条件的License文件。

cat License.dat

MAC : 00:50:56:b1:01:e5
Date : 20240520
Sign : 7390719b409fcfb51a9d52fbcfe8f5d86fd6e4a1e5be2bf38280e9f7ec2eb5bc

其中Sign部分是以上明文信息的加密,用作校验。

三、核验license文件

在原始的eda.py脚本中,我们需要载入这个License文件,并做如下几件事情:

解析License文件,获取MAC/Date/Sign信息。

解密Sign信息。

对比解密的Sign信息和MAC/Date信息,看License文件是否被篡改。

获取当前机器MAC信息和当前Date信息。

将当前机器MAC信息和当前Date信息同Sign的解密信息核验,看是否超限。

       那么我们把如上的License核验步骤加入到eda.py中,得到了新的代码如下。

#!/usr/bin/env python3
import os
import re
import sys
import datetime
import subprocess
from Crypto.Cipher import AES
from binascii import a2b_hex

## License check

def license_check():

    license_dic = parse_license_file()

    sign = decrypt(license_dic['Sign'])

    sign_list = sign.split('#')

    mac = sign_list[0].strip()

    date = sign_list[1].strip()



    # Check license file is modified or not.

    if (mac != license_dic['MAC']) or (date != license_dic['Date']):

        print('*Error*: License file is modified!')

        sys.exit(1)

    # Check MAC and effective date invalid or not.

    if len(sign_list) == 2:

        mac = get_mac()

        current_date = datetime.datetime.now().strftime('%Y%m%d')
        #print(current_date)

        # Must run this script under specified MAC.

        if sign_list[0] != mac:

            print('*Error*: Invalid host!')

            sys.exit(1)

        # Current time must be before effective date.
        #print(sign_list[1])
        if sign_list[1] < current_date:

            print('*Error*: License is expired!')

            sys.exit(1)

    else:
        print('*Error*: Wrong Sign setting on license file.')

        sys.exit(1)

def parse_license_file():

    license_dic = {}

    license_file = './License.dat'



    with open(license_file, 'r') as LF:

        for line in LF.readlines():

            if re.match('^\s*(\S+)\s*:\s*(\S+)\s*$', line):

                my_match = re.match('^\s*(\S+)\s*:\s*(\S+)\s*$', line)

                license_dic[my_match.group(1)] = my_match.group(2)

    return(license_dic)

def decrypt(content):

    aes = AES.new(b'2024052020240520', AES.MODE_CBC, b'2024052020240520')

    decrypted_content = aes.decrypt(a2b_hex(content.encode('utf-8')))

    return(decrypted_content.decode('utf-8'))

def get_mac():
    mac = ''

    SP = subprocess.Popen('/sbin/ifconfig ens192', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                          stderr=subprocess.PIPE)
    (stdout, stderr) = SP.communicate()

    for line in str(stdout, 'utf-8').split('\n'):
        if re.match('^\s*ether\s+(\S+)\s+.*$', line):
            my_match = re.match('^\s*ether\s+(\S+)\s+.*$', line)

            mac = my_match.group(1)
#            print(mac)
            break

    return (mac)

# Main function.

def function():

    print('I am an EDA tool!')

license_check()

function()

执行一下,效果如下。

[15:00:41root@bwglicense_encrypt]#./top.py
I am an EDA tool!

由于我设置的日期是来年,所以成功,测验去年时间,如下

./eda.py
*Error*: License is expired!

四、Python加密

基于Cython的加密方案。

基于Cython的加密方案需要注意两点:

.so文件可以被Python文件import,但是不可以直接运行。

执行Cython加密脚本的Python版本需要同执行Python工具的Python版本保持一致,能够显著减少代码兼容性问题。

       第一步,我们需要改造eda.py,将核心代码移到新文件(比如top.py),顶层脚本eda.py只保留一个空壳,同时把核心功能通过import的方式引入。

       改造后,核心脚本top.py的内容如下。同原先的eda.py相比,执行函数license_check()和function()被移除了,其它一致。

       而改造后的eda.py则成了一个空壳子,如下。

vim eda.py

#!/usr/bin/env python3
import top

top.license_check()
top.function()

然后我们尝试通过Cython将top.py的内容加密。这个过程主要是借助Cython和disutils两个pyton库来实现。

       首先,我们写一个setup.py文件。

vim setup.py

import os
from distutils.core import setup
from Cython.Build import cythonize

py_files = ['top.py',]

setup(ext_modules = cythonize(py_files),)

然后用指定Python脚本来执行setup.py。

[15:11:28root@bwglicense_encrypt]# python3 setup.py build_ext --inplace

running build_ext
building 'top' extension
gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -I/usr/include/python3.6m -c top.c -o build/temp.linux-x86_64-3.6/top.o
gcc -pthread -shared -Wl,-z,relro -g build/temp.linux-x86_64-3.6/top.o -L/usr/lib64 -lpython3.6m -o /data/python/license_encrypt/top.cpython-36m-x86_64-linux-gnu.so

若执行出现以下出现报错

top.c:28:20: fatal error: Python.h: No such file or directory

安装包解决

yum install python3-devel

我们看到新生成了.c .so文件和build目录,其中只有.so文件对我们是有用的。

#ls

build  eda.py  License.dat  license.py  __pycache__  setup.py  top.c  top.cpython-36m-x86_64-linux-gnu.so  top.py

 我们清理无用文件(包括top.py),然后将.so文件更名为top.so(去掉cpython***那一堆字符串)

#ls
eda.py  License.dat  license.py  __pycache__  top.so

然后重新执行eda.py。

./eda.py
I am an EDA tool!

而此时我们看一下.so文件中的核心代码。看不明白就对了

  • 34
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值