4.3. 详解可独立运行的加密脚本
Pyarmor 8.0 没有像之前的版本提供命令 pack 来生成可以独立运行的加密脚本,而是直接通过一个选项 --pack
来告诉 Pyarmor 需要在加密之后自动进行打包。
4.3.1. 自动打包模式
在 8.5.4 版本加入.
选项 --pack
接受两个值
onefile
onedir
这是 PyInstaller 提供的两种打包模式,单文件和单目录。实际上 Pyarmor 主要功能还是加密,加密完成之后生成可以独立运行的包完全是调用 PyInstaller 的相关功能。如果没有安装 PyInstaller 必须首先安装:
$ pip install pyinstaller
假设有一个这样的项目,其目录结构如下:
project/
├── foo.py
├── queens.py
└── joker/
├── __init__.py
├── queens.py
└── config.json
我们使用下面的命令进行打包:
$ cd project
$ pyarmor gen --pack onefile foo.py
那么,Pyarmor 是如何生成一个包含加密脚本的可执行包呢?
首先 Pyarmor 会分析没有加密的脚本 foo.py 的代码,发现它需要导入模块 queens.py 和包 joker
接下来 Pyarmor 会加密这三项到一个临时目录 .pyarmor/pack/dist
然后 Pyarmor 调用 PyInstaller ,让它分析没有加密脚本的 foo.py 依赖关系,把 foo.py 使用到的所有系统包都记录保存下来
最后 Pyarmor 再次调用 PyInstaller ,把临时目录 .pyarmor/pack/dist 下面的加密脚本,以及上一个步骤中发现的系统包 1 统统打包到一个文件里面,最后输出一个可执行文件 dist/foo 。
现在,让我们运行一下最终打好的包 dist/foo 或者 dist/foo.exe:
$ ls dist/foo
$ dist/foo
如果需要生成单个目录的包,只需要传递 onedir 给 --pack
即可:
$ pyarmor gen --pack onedir foo.py
$ ls dist/foo
$ dist/foo/foo
- 1
系统包没有进行加密
4.3.1.1. 检查打包的脚本是否被加密
在脚本 foo.py
或者 joker/__init__.py
增加一行
print('this is __pyarmor__', __pyarmor__)
如果被加密了,那么可以正确打印出来。如果没有加密,就会抛出异常,因为只有加密脚本中才有内置名称 __pyarmor__
的定义。
4.3.1.2. 使用其他 PyInstaller 选项
如果需要为应用程序增加图标,不显示控制台窗口等,只需要把 PyInstaller 的相关选项通过配置项 pack:pyi_options
传递给 Pyarmor 即可。
例如,使用 PyInstaller 选项 -w
不显示控制台窗口:
$ pyarmor cfg pack:pyi_options = "-w"
接下来我们添加另外一个选项 -i
设置图标,需要注意的是在选项 -i
和其值之间必须使用一个空格进行分隔,不要使用等号 =
。例如:
$ pyarmor cfg pack:pyi_options ^ "-i favion.ico"
在添加另外一个选项 --add-data
:
$ pyarmor cfg pack:pyi_options ^ "--add-data joker/config.json:joker"
参见
4.3.1.3. 使用更多的加密选项
在 Darwin 系统,如果想让加密脚本能够同时工作在 Intel 和 Apple M1 框架下,需要传递额外的加密选项 --platform darwin.x86_64,darwin.arm64
:
$ pyarmor gen --pack onefile --platform darwin.x86_64,darwin.arm64 foo.py
其他加密选项也都可以根据需要选用来增加安全性或者提高性能。但是有些选项可能无法使用,例如,--restrict
就无法和 --pack
一起使用。
4.3.2. 自己动手打包加密脚本
如果使用上面的方式出现问题,或者打包好的可执行文件出现执行错误,那么请使用下面的方式自己打包加密脚本。
这需要你了解 如何使用 PyInstaller 和 如何使用 spec file ,如果还不知道如何使用,请点击链接学习相关知识。
下面我们使用一个例子来说明如何手动打包加密脚本 /path/to/src/foo.py
首先使用 Pyarmor 加密这个脚本 2:
$ cd /path/to/src $ pyarmor gen -O obfdist -a foo.py
-
$ mv obfdist/pyarmor_runtime_000000 ./
如果已经有
foo.spec
,需要把运行辅助包增加到hiddenimports
a = Analysis(
...
hiddenimports=['pyarmor_runtime_000000'],
...
)
如果还没有这个文件,使用下面的命令生成
foo.spec
4:$ pyi-makespec --hidden-import pyarmor_runtime_000000 foo.py
修改
foo.spec
,插入补丁代码到a = Analysis
之后,这一步是重点
a = Analysis(
...
)
# Pyarmor patch start:
def pyarmor_patcher(src, obfdist):
# Make sure both of them are absolute paths
src = os.path.abspath(src)
obfdist = os.path.abspath(obfdist)
count = 0
for i in range(len(a.scripts)):
if a.scripts[i][1].startswith(src):
x = a.scripts[i][1].replace(src, obfdist)
if os.path.exists(x):
a.scripts[i] = a.scripts[i][0], x, a.scripts[i][2]
count += 1
if count == 0:
raise RuntimeError('No obfuscated script found')
for i in range(len(a.pure)):
if a.pure[i][1].startswith(src):
x = a.pure[i][1].replace(src, obfdist)
if os.path.exists(x):
if hasattr(a.pure, '_code_cache'):
with open(x) as f:
a.pure._code_cache[a.pure[i][0]] = compile(f.read(), a.pure[i][1], 'exec')
a.pure[i] = a.pure[i][0], x, a.pure[i][2]
pyarmor_patcher(r'/path/to/src', r'/path/to/obfdist')
# Pyarmor patch end.
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
最后直接使用打过补丁的
foo.spec
来打包,同时使用选项 --clean 避免补丁因为缓存的文件而失效:$ pyinstaller --clean foo.spec
请根据你的具体情况,做如下修改
使用实际目录替换
/path/to/src
和/path/to/obfdist
使用实际名称替换
pyarmor_runtime_000000
如何验证打包进去的是加密脚本
方法一,可以在 foo.spec
的补丁代码增加一些 print 语句,验证加密脚本已经替换了原来的脚本
方法二,可以在主脚本增加一些调试语句进行判断,例如
print('this is __pyarmor__', __pyarmor__)
如果不是加密脚本,这个语句会报错,只有在加密脚本中才能正常打印。
备注
4.3.3. 替换打包模式
自 8.5.4 版本弃用: 现在推荐使用选项 --pack
的 onefile
或者 onedir
模式
首先调用 PyInstaller 将脚本打包成为单独的可执行文件或者打包到一个目录,然后把打包生成的可执行文件通过选项 --pack
传递给 pyarmor gen 来实现:
$ pyinstaller foo.py
$ pyarmor gen --pack dist/foo/foo foo.py
Pyarmor 首先加密脚本,并把它们被存放到 .pyarmor/pack/dist
,然后进行下面的额外处理:
提取可执行文件中内容到一个临时目录
.pyarmor/pack/
使用加密脚本替换临时目录中同名的未加密脚本
把加密脚本的 运行辅助文件 增加到临时目录中
根据把临时目录中所有内容重新生成可执行文件,并替换原来的可执行文件
需要注意的是,在这种方式下面,使用 PyInstaller 6.0+ 打包生成的可执行文件无法被正确处理,只能使用低版本 PyInstaller 进行打包。
重要
只有命令行列出的脚本会被加密,如果需要加密其他脚本或者子目录,在命令行列出它们。例如:
$ pyarmor gen --pack dist/foo/foo -r *.py dir1 dir2 ...
4.3.4. Apple M1 的 segment fault
在 Apple M1 上打包,如果生产的加密脚本运行时候发生崩溃,请首先检查运行辅助包的签名:
$ codesign -v dist/foo/pyarmor_runtime_000000/pyarmor_runtime.so
如果签名非法,请重新进行签名:
$ codesign -f -s dist/foo/pyarmor_runtime_000000/pyarmor_runtime.so
如果使用了 --enable-bcc
或者 --enable-jit
进行加密,那么还需要启用 Allow Execution of JIT-compiled Code Entitlement