我们在进行Python技术开发时,特别是便脚本、组件、工具类开发的时候,在提测或上线的时候需要把实际的工具脚本交由实际执行者,未免一些代码内的敏感信息泄漏或者可以随意更改组件工具导致一些不可控制的问题。我们常常还是会采取一系列手段,对Python代码进行加密、编译、混淆以保护我们的工程。
现介绍以下几种源码保护手段:
1.编译.pyc文件
pyc文件是一种二进制文件,由原生Python文件经过编译后所生成的,py文件编译成pyc文件后加载速度更快而且提高了代码的安全性。pyc的内容与python的版本相关,不同版本编译的pyc文件不一样
单个pyc文件的生成
一般正常情况下,我们运行py脚本,其包括所依赖的脚本都会在所在目录下自动编译生成.pyc文件。这次我们主要看下如何通过命令方式主动生成.pyc文件
python -m py_compile py文件绝对路径
pyc会生成在目录下的__pycache__下(前后有双下划线)
生成的文件名命名方式:源文件名.cpython-python版本.pyc
批量pyc文件的生成
通过compile_dir方法指定目录,会将其目录下所有py文件全部编译为pyc文件
compile_dir(dir[, maxlevels[, ddir[, force[, rx[, quiet]]]]])
参数含义:
– maxlevels:递归编译的层数
– ddir:If ddir is given, it is prepended to the path to each file being compiled for use in compilation time tracebacks, and is also compiled in to the byte-code file, where it will be used in tracebacks and other messages in cases where the source file does not exist at the time the byte-code file is executed.
– force:如果True,不论是是否有pyc,都重新编译
– rx:一个正则表达式,排除掉不想要的目录
– quiet:如果为True,则编译不会在标准输出中打印信息
python -m compileall <dir>
2.打包为exe
打包Python程序的三方开源库很多,这里主要推荐两种常用且效果不错的打包库:PyInstaller、Nuitka
名称 | License | Qt6 | Qt5 | Linux | Windows | macOS |
---|---|---|---|---|---|---|
PyInstaller | GPL | 部分支持 | 支持 | 支持 | 支持 | 支持 |
Nuitka | MIT | 支持 | 支持 | 支持 | 支持 | 支持 |
PyInstaller
PyInstaller是一个第三方库,它能够在Windows、Linux、 Mac OS X 等操作系统下将 Python源文件进行打包,打包完成后,exe即可在没有Python 的环境中运行,也可以作为一个独立文件方便传递和管理。
PyInstaller可以在Windows、Mac OS X和Linux上使用,但是并不是跨平台的,所以打包成.exe文件,需要在Windows系统上运行PyInstaller进行打包工作;打包成mac app,需要在Mac OS上使用。
安装:
pip install pyinstaller
使用:
Pyinstaller -F demo.py -- 打包exe
Pyinstaller -F -w demo.py -- 不带控制台的打包 不带黑框
Pyinstaller -F -i test.ico demo.py -- 打包指定exe图标打包
常用选项:
-h,–help 查看该模块的帮助信息
-F,-onefile 产生单个的可执行文件
-D,–onedir 产生一个目录(包含多个文件)作为可执行程序
-a,–ascii 不包含 Unicode 字符集支持
-d,–debug 产生 debug 版本的可执行文件
-w,–windowed,–noconsolc 指定程序运行时不显示命令行窗口(仅对 Windows 有效)
-c,–nowindowed,–console 指定使用命令行窗口运行程序(仅对 Windows 有效)
-o DIR,–out=DIR 指定 spec 文件的生成目录。如果没有指定,则默认使用当前目录来生成 spec 文件
-p DIR,–path=DIR 设置 Python 导入模块的路径(和设置 PYTHONPATH 环境变量的作用相似)。也可使用路径分隔符(Windows 使用分号,Linux 使用冒号)来分隔多个路径
-n NAME,–name=NAME 指定项目(产生的 spec)名字。如果省略该选项,那么第一个脚本的主文件名将作为 spec 的名字
-i 选择图标
pyinstaller将多个py文件打包
生成spec规范配置文件:
pyi-makespec demo.py
或
pyinstaller [-F/-D] [-w/-c] [-i xxx.ico] demo.py
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None #设置 加密,需要安装tinyaes第三方库,最多16位字符,此处在使用--key= 会有变化
a = Analysis(
['demo.py'], # 运行的所有py文件,包括依赖的py文件
pathex=[], # 搜索导入的路径列表(此列表为项目绝对路径),包括选项给出的路径--paths,项目需要从什么地方导入自定义库
binaries=[], # 脚本需要的非python模块,包括--add-binary选项给出的名称,二进制数据
datas=[], # 应用程序中包含的非二进制文件,包括--add-data选项给出的名称,项目需要用到什么数据,比如图片,视频等。里面格式为tuple,第一个参数是文件路径,第二个是打包后所在的路径,其为一个元组:('image/*.png','data/image')
hiddenimports=[], # 假如打包后打开exe显示module not found,就要把该库添加到hiddenimports里面了
hookspath=[],
hooksconfig={}, # 挂钩配置选项由一个字典组成
runtime_hooks=[],
excludes=[], # 假如你用的python有很多库,但是你不需要用到某个,那么就把它添加到里面去,可以压缩文件大小
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts, # 打包的脚本文件
# a.binaries, # 如果是单文件模式,则需要添加;多文件也可以添加
# a.zipfiles,
# a.datas,
[],
exclude_binaries=True, # 是否排除二进制文件,为True时,为排除二进制的文件,当文件交大时包含二进制文件运行较快,如果是单文件,则没有这个选项
name='demo', # 打包程序的名字
debug=False, # 是否启用调试功能
bootloader_ignore_signals=False,
# runtime_tmpdir=None, # 生成单文件时需要这个参数,定义运行时的临时文件夹
strip=False,
upx=True, # 打包的时候进行压缩,False表示不压缩;要用到一个压缩程序UPX,用于压缩文件,需要单独下载
console=True, # 打包后的可执行文件双击运行时屏幕会出现一个cmd窗口,不影响原程序运行,等于是是否加-w参数
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
"""添加选项,初始化时没有的"""
icon="", # 指定应用程序的图标,传入路径,可以相对路径
)
coll = COLLECT(
"""
如果是单文件模式,不需要这个COLLECT类,同时需要将:
a.binaries,
a.zipfiles,
a.datas,
这些数据文件添加到EXE中
"""
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='main',
)
项目打包:
pyinstaller demo.spec
这样即可将python程序打包成一个独立的应用工具,直接运行即可。实现了保护程序的一个作用(当然也是可以反编译成功的,不过也提升了破解的难度)
Nuitka
nuitka则是将python源码转成C++(这里得到的是二进制的pyd文件,防止了反编译),然后再编译成可执行文件。
安装:
pip install -U nuitka
安装MinGW64:
Windows环境需安装MinGW64,搭建gcc编译环境
MacOS环境不需要单独下载,它会在安装nuitka时自动下载gcc
https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/
打包:
nuitka --mingw64 xxx.py # 单纯打包
nuitka --mingw64 --standalone --show-progress --show-memory --enable-plugin=pyqt5 --output-dir=out demo.py # 指定参数打包
选项说明:
--mingw64:环境选择
--standalone:是否独立环境
--show-progress:展示过程
--show-memory:显示内存
--plugin-enable=qt-plugins:qt配置
--include-qt-plugins=sensible,styles:qt样式保持
--recurse-all:导入引入的资源
nuitka打包并不像网上说的那样,打包速度快,打包体积小。我也实测过,目前体检的感觉就是资源方面的确不用我们考虑那么深,不像p yin staller可能相关配置就弄好了,打包还是会报资源缺失。而且打包的速度比较慢,体积也挺大的,但是毕竟也是可达到保护Python程序的目的,给使用者方便。
3.代码Obfuscator混淆
借助oxyry在线网站实现obfuscator混淆
使用 pyobfuscate 库进行混淆
pyobfuscate 会对代码中用户定义的类、函数、变量等进行重命名、更改代码缩进(默认1)、移除注释、添加不影响逻辑的代码语句,最终起到混淆的作用。不过 pyobfuscate 使用 Python2 编写,无法解析 Python3 中的 f-string 等特殊语法,因此使用前需要将源码进行一定程度的修改,当然也可以直接修改 pyobfuscate 库,增加对 Python3 版本的支持。
python2 pyobfuscate.py demo.py > demo_obfuscated.py
两种主要都是对类和函数名等进行了重置替换,增加阅读难度
4.使用 Cython
使用Cython进行开发
编写文件hello.pyx或hello.py:
def hello():
print('hello')
编写setup.py:
from distutils.core import setup
from Cython.Build import cythonize
setup(name='Hello World app',
ext_modules=cythonize('hello.pyx'))
编译为.c,再进一步编译为.so或.pyd:
python setup.py build_ext --inplace
python -c "from hello import hello;hello()"即可直接引用生成的二进制文件中的hello()函数。
生成的二进制 .so 或 .pyd 文件难以破解同时带来了性能提升
5.PyArmor
PyArmor 是一个用于加密和保护 Python 脚本的工具。它能够在运行时刻保护 Python脚本的二进制代码不被泄露,设置加密后 Python 源代码的有效期限,绑 定加密后的Python源代码到硬盘、网卡等硬件设备。它的保障机制主要包括:
- 加密编译后的代码块,保护模块中的字符串和常量
- 在脚本运行时候动态加密和解密每一个函数(代码块)的二进制代码
- 代码块执行完成之后清空堆栈局部变量
- 通过授权文件限制加密后脚本的有效期和设备环境
除了对 Python 代码进行加密,PyArmor 还能设置 Python 程序的许可方式,比如设置程序的使用期限、设置允许运行的设备、扩展其他认证方式等
安装:
pip install pyarmor
使用 obfuscate 选项对代码进行加密:
pyarmor obfuscate demo.py
使用 licenses 选项生成许可文件:
pyarmor licenses \
--expired "2023-03-11" \
--bind-disk "10030SFTUF3NJ5T" \
--bind-mac "56:f1:a7:64:f0:64" \
--bind-ipv4 "211.110.6.54" \
r001
使用 pack 选项即可打包脚本:
pyarmor pack demo.py
不过这款工具属于试用阶段,倾向于商业模式,可以学习试试。
以上5种方式都可提升程序的安全性,保护项目敏感信息。如果对外发布的,也可提升破解难度。今天的分享就到这里了,欢迎留言讨论。