0%

python项目文件加密与license控制

python项目交付给客户时,为了安全起见,可以将其加密,而常见的加密方式pyc并不安全,可以很轻易的被破解。本文提供了一种对python项目加密的方式,为每个py文件加密为so文件,难以破解。同时,本文提供了一种license授权机制,可以指定加密项目在指定机器上运行,且可以配置授权时间。

项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
├── app
│   ├── create_app.py
│   ├── __init__.py
│   ├── license_utils.py
│   ├── manage
│   │   ├── __init__.py
│   │   ├── m1.py
├── readme.md
├── run.py
└── setup.py

项目地址:https://github.com/fushengwuyu/encrypt_license.git

1. 生成license文件

license代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import re
import sys
import datetime
import subprocess
from Crypto.Cipher import AES
from binascii import a2b_hex
from binascii import b2a_hex


class LicenseEncode:
def __init__(self, mac, license_path, expired_date=None):
self.mac = mac
self.license_path = license_path
self.date = (datetime.datetime.now() + datetime.timedelta(days=30)).strftime(
'%Y%m%d') if expired_date is None else expired_date

def encrypt(self, 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'2021052020210520', AES.MODE_CBC, b'2021052020210520')
encrypted_content = aes.encrypt(content)
return (b2a_hex(encrypted_content))

def gen_license_file(self):
with open(self.license_path, 'w') as LF:
LF.write('MAC : %s\n' % (self.mac))

LF.write('Date : %s\n' % (self.date))

sign = self.encrypt('%s#%s' % (self.mac, self.date))
print('Sign : ' + str(sign.decode('utf-8')) + '\n')
LF.write('Sign : ' + str(sign.decode('utf-8')) + '\n')


class LicenseDecode:
def __init__(self, license_path):
self.license_path = license_path

def license_check(self):
license_dic = self.parse_license_file()
sign = self.decrypt(license_dic['Sign'])
sign_list = sign.split('#')
mac = sign_list[0].strip()
date = sign_list[1].strip()
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:
macs = self.get_mac()
current_date = datetime.datetime.now().strftime('%Y%m%d')
if sign_list[0] not in macs:
print('*Error*: Invalid host!')
sys.exit(1)
# Current time must be before effective date.

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(self):
license_dic = {}

with open(self.license_path, '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(self, content):
aes = AES.new(b'2021052020210520', AES.MODE_CBC, b'2021052020210520')
decrypted_content = aes.decrypt(a2b_hex(content.encode('utf-8')))
return (decrypted_content.decode('utf-8'))

def get_mac(self):
SP = subprocess.Popen('/sbin/ifconfig', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(stdout, stderr) = SP.communicate()
macs = []
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)
macs.append(mac)
return macs


if __name__ == '__main__':
# make license file
mac = '00:28:f8:fa:25:bc' # 改为需授权的主机的mac地址

# 第一步,生成license文件
LicenseEncode(mac, './License.dat').gen_license_file()

执行脚本,生成license.dat的授权文件。

2. 验证license

执行run.py

1
2
3
4
5
6
7
8
9
10
/home/sunshine/python/encrypt_license/app/license_utils.py:75: DeprecationWarning: invalid escape sequence \s
if re.match('^\s*(\S+)\s*:\s*(\S+)\s*$', line):
[2022-04-22 10:13:28 +0800] [201518] [INFO] Sanic v21.12.0
[2022-04-22 10:13:28 +0800] [201518] [INFO] Goin' Fast @ http://0.0.0.0:5008
[2022-04-22 10:13:28 +0800] [201518] [INFO] mode: production, single worker
[2022-04-22 10:13:28 +0800] [201518] [INFO] server: sanic
[2022-04-22 10:13:28 +0800] [201518] [INFO] python: 3.8.10
[2022-04-22 10:13:28 +0800] [201518] [INFO] platform: Linux-5.13.0-39-generic-x86_64-with-glibc2.29
[2022-04-22 10:13:28 +0800] [201518] [INFO] packages: sanic-routing==0.7.1
[2022-04-22 10:13:28 +0800] [201518] [INFO] Starting worker [201518]

验证成功。

尝试修改license.dat

1
*Error*: License file is modified!

3. 加密整个项目

执行setup.py

1
2
3
x86_64-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I/home/sunshine/envs/py3.8/include -I/usr/include/python3.8 -c setup.c -o build/temp/setup.o
x86_64-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fwrapv -O2 -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 build/temp/setup.o -o build/setup.cpython-38-x86_64-linux-gnu.so
complate! time: 7.539966344833374 s

执行完成之后,会在当前目录生成build文件夹,该文件夹就是加密后的项目,将整个build文件夹交付客户即可。

build文件夹目录如下:

1
2
3
4
5
6
7
8
9
10
11
12
(py3.8) sunshine@sunshine-ThinkPad-T470p:~/python/encrypt_license$ tree build/
build/
├── app
│   ├── create_app.cpython-38-x86_64-linux-gnu.so
│   ├── license_utils.cpython-38-x86_64-linux-gnu.so
│   └── manage
│   └── m1.cpython-38-x86_64-linux-gnu.so
├── License.dat
├── readme.md
├── run.cpython-38-x86_64-linux-gnu.so
├── run.py
└── setup.cpython-38-x86_64-linux-gnu.so

验证一下build/run.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Sanic v21.12.0 │
│ Goin' Fast @ http://0.0.0.0:5008 │
├───────────────────────┬─────────────────────────────────────────────────────────┤
│ │ mode: production, single worker │
│ ▄███ █████ ██ │ server: sanic │
│ ██ │ python: 3.8.10 │
│ ▀███████ ███▄ │ platform: Linux-5.13.0-39-generic-x86_64-with-glibc2.29 │
│ ██ │ packages: sanic-routing==0.7.1 │
│ ████ ████████▀ │ │
│ │ │
│ Build Fast. Run Fast. │ │
└───────────────────────┴─────────────────────────────────────────────────────────┘

[2022-04-22 10:21:13 +0800] [202771] [WARNING] Sanic is running in PRODUCTION mode. Consider using '--debug' or '--dev' while actively developing your application.
[2022-04-22 10:21:13 +0800] [202771] [INFO] Starting worker [202771]

成功~~