Python作为动态语言一般是以源码方式进行部署的,这就意味着他人在部署机器上可以直接获取项目代码,可能给作者带来不必要的损失和风险,这就需要对代码进行加密或混淆。常规的几类加密(混淆)方式如下:
总体来说1和2的方式还是存在一定的风险,本文将采用第三种方式,且提供五种该类型的加密方式,分别为、Cython、cryptography、RSA、pyAesCrypt和encryptpy。
该方法是通过Cython将python文件编译成.c(C语言)文件,再生成.so(share object)文件,这种方式是不可逆的,所以能够起到混淆代码,起到一定程度的加密效果。
通过命令查看目录结构
$ tree -a .
目录结构如下:
.
├── setup.py
└── example
├── __init__.py
├── main.py
├── README.md
├── xxx.py
└── utils.py
1 directory, 6 files
运行命令
python setup.py build_ext
setup.py文件如下,这段代码可通用,直接运行即可自动生成
# -*- coding: utf-8 -*-
import os
import shutil
import fnmatch
from distutils.core import setup
from Cython.Build import cythonize
def get_delete_files(project_root):
"""这个函数会遍历指定项目根目录下的所有文件,并返回一个包含所有Python文件的列表。 这个函数会排除名为'main.py'的文件,因为它是主程序文件。"""
matches = []
for root, dirnames, filenames in os.walk(project_root):
for filename in fnmatch.filter(filenames, '*.py'):
if filename != 'main.py': # 排除main.py文件
matches.append(os.path.join(root, filename))
for filename in fnmatch.filter(filenames, '*.c'):
matches.append(os.path.join(root, filename))
return matches
def get_py_files(project_root):
"""这个函数会遍历指定项目根目录下的所有文件,并返回一个包含所有Python文件的列表。 这个函数会排除名为'main.py'的文件,因为它是主程序文件。"""
matches = []
for root, dirnames, filenames in os.walk(project_root):
for filename in fnmatch.filter(filenames, '*.py'):
if filename != 'main.py': # 排除main.py文件
matches.append(os.path.join(root, filename))
return matches
def tool(root, paths):
"""
该函数是执行整个任务的主要函数。它以项目的根目录和Python文件的路径列表作为输入。
:param root:
:param paths:
:return:
"""
filePath3 = os.path.dirname(dis_root) + '/build/'
if not os.path.exists(filePath3):
os.mkdir(filePath3)
# 1、文件加密
setup(name='encrypt', ext_modules=cythonize(paths))
print("加密完成")
# 2、将加密的文件移至对应目录下
files_1 = os.listdir(filePath3)
for files_1_temp in files_1:
if "lib" in files_1_temp:
files_1 = files_1_temp
# print(files_1)
print('filePath3: ', filePath3)
print('files_1: ', files_1)
for files_2 in os.listdir(filePath3 + files_1):
so_file = filePath3 + files_1+"/" + files_2
print(so_file)
# 文件移动或拷贝
shutil.move(so_file, root)
# 3、删除原文件和生成的附属文件夹
files2 = get_delete_files(dis_root)
for file in files2:
if os.path.exists(file):
os.remove(file)
print('移除后test 目录下有文件:%s' % file)
else:
print("要删除的文件不存在!")
# 删除附属文件夹
try:
shutil.rmtree(filePath3)
except Exception as ex:
print("错误信息:"+str(ex)) # 提示:错误信息,目录不是空的
print("删除完成")
dis_root = '/mnt/xxx/example'
Paths = get_py_files(dis_root)
tool(dis_root, Paths)
环境安装
pip install cryptography
下面以加密一个onnx文件为例。
先进行加密,可根据配置密钥个数,保障加密强度。然后对文件内容进行加密,当然这个密钥后面在对文件进行解密的时候会派上用场,因此密钥一定要保存完好,代码如下:
import os
import base64
import argparse
from cryptography.fernet import Fernet, MultiFernet
def Parser():
parser = argparse.ArgumentParser(description='文件加密')
parser.add_argument('--model_file', '-mf', type=str, default=None, help='需要加密文件的绝对地路径')
parser.add_argument('--new_model_file', '-nmf', type=str, default=None, help='文件保存的绝对地路径')
parser.add_argument('--key_num', '-kn', type=int, default=8, help='密钥数量')
return parser.parse_args()
def generate_key(message='') -> bytes:
x = os.urandom(32 - len(message)) + message.encode()
return base64.urlsafe_b64encode(x)
def add_secret(model_file=None, new_model_file=None, multi_key: int = 1):
if new_model_file is None:
ext = os.path.splitext(model_file)[-1]
new_model_file = model_file.replace(ext, '.dll')
# # 生成密钥
ks = []
keys = []
if multi_key > 0:
for i in range(multi_key):
keyi = generate_key()
keys.append(keyi)
print(f'key{i + 1}: ', keyi)
ki = Fernet(keyi)
ks.append(ki)
if len(ks) == 1:
f = ks[0]
else:
f = MultiFernet(ks)
else:
raise ValueError('The "multi_key" must be greater than 0.')
# 保存license
file = 'license.dll'
with open(file, 'wb') as fw:
for j in keys:
fw.write(j + b'\n')
print('liscence file write in ', file)
with open(new_model_file, 'wb') as ew:
# 二进制读取模型文件
content = open(model_file, 'rb').read()
# 根据密钥解密文件
encrypted_content = f.encrypt(content)
# print(encrypted_content)
# 保存到新文件
ew.write(encrypted_content)
def desecrect(new_model_file: str = None, keys_file: str = None):
"""
:param new_model_file:
:param keys:
:return:
"""
with open(keys_file, 'rb') as f:
x = f.read()
keys = x.strip(b'\n').split(b'\n')
print(keys)
ks = []
for keyi in keys:
ki = Fernet(keyi)
ks.append(ki)
if len(ks) > 1:
f = MultiFernet(ks)
else:
f = ks[0]
onnx_file = open(new_model_file, 'rb').read()
onnx_file = f.decrypt(onnx_file)
if onnx_file is not None:
print('解密成功')
with open(desecrect_model_file, 'wb') as ew:
ew.write(onnx_file)
def test():
dll_file = 'xxx.dll'
key1 = 'license.dll'
desecrect(dll_file, key1)
def main():
args = Parser()
if args.model_file is None:
model_file = 'xxx.onnx'
else:
model_file = args.model_file
message = args.message if args.message is not None else ''
add_secret(message, model_file, args.new_model_file, args.key_num)
if __name__ == '__main__':
main()
# test()
使用RSA加密算法实现数据的加密解密
环境安装
pip install rsa
import os
import rsa
def encrypt_file(file_path, public_key_file):
"""使用RSA算法加密文件
参数:
file_path: 需要加密的文件路径
public_key_file: 公钥文件路径
返回值:
无
"""
# 读取文件内容
with open(file_path, "rb") as file:
file_content = file.read()
# 读取公钥
with open(public_key_file, "rb") as key_file:
public_key = rsa.PublicKey.load_pkcs1(key_file.read())
# 加密文件内容
encrypted_content = rsa.encrypt(file_content, public_key)
# 将加密后的内容写入文件
with open(file_path, "wb") as file:
file.write(encrypted_content)
def decrypt_file(file_path, private_key_file, password):
"""使用RSA算法解密文件
参数:
file_path: 需要解密的文件路径
private_key_file: 私钥文件路径
password: 私钥文件密码
返回值:
无
"""
# 读取文件内容
with open(file_path, "rb") as file:
encrypted_content = file.read()
# 读取私钥
with open(private_key_file, "rb") as key_file:
private_key = rsa.PrivateKey.load_pkcs1(key_file.read(), password)
# 解密文件内容
file_content = rsa.decrypt(encrypted_content, private_key)
# 将解密后的内容写入文件
with open(file_path, "wb") as file:
file.write(file_content)
个人觉得这个代码库实现最为简单,只是密钥需要我们自己指定的
环境安装
pip install pyAesCrypt
import pyAesCrypt
def Encryption(input_file_path, output_file_path, key):
pyAesCrypt.encryptFile(input_file_path, output_file_path, key)
print("File has been decrypted")
def Decryption(input_file_path, output_file_path, key):
pyAesCrypt.decryptFile(input_file_path, output_file_path, key)
print("File has been decrypted")
encryptpy使用Cython将Python代码编译为二进制以达到加密的目的,并且支持通过git-diff来获取两次提交间的差异文件,方便地进行编译
环境安装
pip install encryptpy
Basic Usage
Usage: encryptpy [OPTIONS] COMMAND [ARGS]...
Encrypt your Python code
Options:
--config TEXT The config file, ignore if given is invalid [default:
.encryptpy.cfg]
--help Show this message and exit.
Commands:
clean Simply clean `build` and `__pycache__` directory in DIRS
git-diff Compile files between two COMMITS, see `git-diff`: `--name-only`
init Copy src to build-dir and do compile, usually used for the...
run Compile given Python code files
子命令用法可以使用encryptpy --help来查看。
例如,有一个名为package_a的项目文件目录:
$ tree -a .
.
├── .encryptpy.cfg
└── package_a
├── __init__.py
├── main.py
├── README.md
├── setup.py
└── utils.py
1 directory, 6 files
其中 .encryptpy.cfg内容如下:
[encryptpy]
; Files will be compiled
paths =
package_a
; Files will be ignored when compiling, support Regex
ignores =
setup.py
; For command `init`, files will be ignored when copying, Glob-style
copy_ignores =
*.pyc
*.md
; The build directory
build_dir = build
; For commands `run` and `git-diff`, whether the source .py will be removed
clean_py = 0
$ encryptpy init .
查看build目录:
$ tree -a build
build
├── .encryptpy.cfg
└── package_a
├── __init__.cpython-38-x86_64-linux-gnu.so
├── main.cpython-38-x86_64-linux-gnu.so
├── setup.py
└── utils.cpython-38-x86_64-linux-gnu.so
1 directory, 5 files
$ encryptpy run package_a/main.py
将package_a/main.py被重新编译为package_amain.cpython-38-x86_64-linux-gnu.so
$ encryptpy git-diff 0.1 0.2
标记(或提交或分支)0.1 和 0.2 之间更改的文件将被编译。