一、引题
我写了一个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文件中的核心代码。看不明白就对了