Python项目加密
交付项目或者产研合作时往往需要保密代码,于是编写了一个加密脚本。
方案是使用Cython将.py文件编译.so文件,内容加密且较难破解。
示例代码setup.py如下,Conda环境配置(编译环境与运行环境python版本需相同)。
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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# -*- coding: utf-8 -*- import os import shutil from distutils.core import setup from distutils.extension import Extension from setuptools import find_packages from Cython.Build import cythonize import shutil print("当前路径 - %s" %os.getcwd()) import sys sys.path.append(os.getcwd()) BASE_PATH = os.path.dirname(__file__) itemsPY = [] itemsPY_dict = {} root_items = [] # 原项目文件夹 source_path = BASE_PATH+"/temp_real_time" # so项目文件夹 so_path = BASE_PATH+"/temp_real_time_so" # 主文件 main_py_origin = source_path+"/app.py" main_py = so_path+"/app.py" print('source_path', source_path) print('BASE_PATH', BASE_PATH) # 判断是否再次生成(仅修改部分文件时) if (os.path.exists(source_path+'_origin') == True): os.rename(source_path, so_path) os.rename(source_path+'_origin', source_path) print('********************************************二次生成********************************************') def markImage(source_path): fileStr = "" for root, dirs, files in os.walk(source_path,): root_items.append(root) itemsPY_dict[root] = [] # sys.path.append(root) for name in files: if name.split(".")[-1] == "py" and name != "setup.py" and name != "PyMain.py": fileStr = os.path.join(root, name) # print("image_file_name=",fileStr) itemsPY.append([fileStr, ]) itemsPY_dict[root].append(fileStr) markImage(source_path) #参数1:python源码目录, 参数2:输出so目标目录 # create so print('********************************************编译********************************************') for item in root_items: # item_l = [item + '\\*.py'] # sys.path.append(item) item_l = itemsPY_dict[item] # item_init = item+'\\__init__.py' # if item_init in item_l: # item_l.remove(item_init) print('*************************', item) for item_i in item_l: item_i_l = len(item_i) source_path_l = len(source_path) module_dir = item_i[(len(source_path)+1):-3] print(module_dir) # if module_dir=='__init__': parent_module = source_path[(len(BASE_PATH)+1):] module_dir = parent_module+'.'+module_dir # print('module_dir', module_dir) module_dir = module_dir.replace('/', '.') print('module_dir', module_dir) extensions = [ Extension( module_dir, sources=[item_i], ) ] setup(name=module_dir, packages=find_packages(), ext_modules=cythonize(extensions)) # setup( # packages=find_packages(), # ext_modules=cythonize([item_i])) #创建目录 if (os.path.exists(so_path) == False): os.mkdir(so_path) # if (os.path.exists("utils") == True): # shutil.move("utils", so_path) #复制库到相应目录 print('********************************************复制so********************************************') for root, dirs, files in os.walk(source_path): # if (so_path in dirs): # continue so_root_path = so_path+root[len(source_path):] print('so_root_path=', so_root_path) if (os.path.exists(so_root_path) == False): os.mkdir(so_root_path) for fileSO in files: if fileSO.split(".")[-1] == "so" and fileSO != "setup.py" and 'build' not in so_root_path: root_fileSO = root + '/' + fileSO so_path_fileSO = so_path + root[len(source_path):] + '/' + fileSO print(root_fileSO, so_root_path, so_path_fileSO) if (fileSO != "license_c.so"): try: shutil.copyfile(root_fileSO, so_path_fileSO) # shutil.move(root_fileSO, so_path_fileSO) except Exception as e: print("error=", e) # rename for root, dirs, files in os.walk(so_path): for name in files: if name.split(".")[-1] == "so" and name != "PyMain.py": fileStr = os.path.join(root, name) print("image_file_name=", fileStr) targetName = fileStr.replace("cpython-39-x86_64-linux-gnu.", "") os.rename(fileStr, targetName) #删除源码目录下生成的.c .so build文件 print('********************************************删除多余文件********************************************') for item in itemsPY: # targetName = item[0].replace(".py", ".so") targetName = item[0][:-3] + '.cpython-39-x86_64-linux-gnu.so' os.system("rm -rf " + targetName) for item in itemsPY: targetName = item[0].replace(".py", ".c") os.system("rm -rf " + targetName) os.system("rm -rf " + BASE_PATH + '/build') # 复制原文件夹下所有固化文件到so_path print('********************************************复制非py文件********************************************') for root, dirs, files in os.walk(source_path): # if (so_path in dirs): # continue so_root_path = so_path+root[len(source_path):] print('so_root_path=', so_root_path) # if (os.path.exists(so_root_path) == False): # os.mkdir(so_root_path) for fileSO in files: if fileSO.split(".")[-1] != "so" and fileSO.split(".")[-1] != "py" and fileSO.split(".")[-1] != "pyc" and 'build' not in so_root_path: root_fileSO = root + '/' + fileSO so_path_fileSO = so_path + root[len(source_path):] + '/' + fileSO print(root_fileSO, so_root_path, so_path_fileSO) # if (fileSO != "license_c.so"): try: shutil.copyfile(root_fileSO, so_path_fileSO) # shutil.move(root_fileSO, so_path_fileSO) except Exception as e: print("error=", e) # 复制主文件 print('********************************************复制主文件********************************************') shutil.copyfile(main_py_origin, main_py) # 重命名so文件夹 print('********************************************重命名项目文件夹********************************************') os.rename(source_path, source_path+'_origin') os.rename(so_path, source_path) print("finish!") |
使用步骤:
1、安装模块Cython
命令:pip install Cython -i https://pypi.tuna.tsinghua.edu.cn/simple
2、编写setup.py文件,
参考目录下的setup.py文件,仅需要修改文件中项目文件夹与目标文件夹地址,并且指定主文件(so代码库入口文件,py格式)。
1 2 3 4 5 6 7 8 |
# 原项目文件夹 原始命名规则即可 source_path = BASE_PATH+"/temp_real_time" # so项目文件夹 so_path = BASE_PATH+"/temp_real_time_so" # 主文件 main_py_origin = source_path+"/serving/app.py" main_py = so_path+"/serving/app.py" |
setup.py文件说明:
功能:
- 修改部分源码后,二次生成时修改文件夹名;
- 读取指定目录下的python文件(包含子目录下的python文件);
- 生成so文件到setup.py指定的source_path;
- 创建so_path,并把当前的so文件复制到so_path下;
- 对生成的so文件进行重新命名去掉”.cpython-39-x86_64-linux-gnu”关键字,防止泄露服务器信息;
- 删除python源码目录下的.c与.so文件,删除BASE_PATH下生成的build文件夹;
- 复制原文件夹下所有非py文件到so_path(例如pkl、xlsx等);
- 复制main_py_origin到so_path;
- 重命名source_path为source_path+’_origin’,重命名so_path与source_path同名。
注意:
1)生成前source_path必须为原始命名,例如本示例中的”/temp_real_time”,否则生成so运行会找不到相应库;
2)setup.py最终会将so_path重命名为BASE_DIR+”/temp_real_time”(即source_path),因为代码执行且各文件相互调用时以source_path为准(因为setup.py文件在temp_real_time项目代码之外,temp_real_time项目中的主文件环境变量,等同于BASE_DIR+”/temp_real_time”);
3)首次生成后source_path会变为source_path+’_origin’,此时无需修改文件夹明,setup.py会自动识别并修改。
3、生成命令
1 2 3 |
# 生成.so文件 python setup.py build_ext --inplace |
注意:
1)运行源码与运行so库的python版本要一致;
2)在windows上只能生成.pyd文件,无法生成.so文件;
3)源码中有错误会生成失败。