1 前言

Python的脚本文件是开源的,对于一些商业软件或量化策略的安全性没有保障。因此需要保护源码。因此就有了 Python 代码进行混淆、加密保护的需求。

  • 混淆代码,可以使用pyminifier。

  • 而加密处理,就比较麻烦。Python有py、pyc、pyw、pyo、pyd等文件格式。

    • 其中,pyc是二进制文件。但很容易被反编译。

    • pyw也不行,只是隐藏命令行界面而已,可以作为入口脚本。

    • pyo和pyc差不多,也容易被反编译。

    • 最后剩下pyd格式。

      pyd格式是D语言(C/C++综合进化版本)生成的二进制文件,实际也会是dll文件。该文件目前位置没找到可以被反编译的消息,只能被反汇编。Sublime text编辑器也是使用该格式。

2 pyc

pyc 是一种二进制文件,是由 Python 文件经过编译后所生成的文件,它是一种 byte code,Python 文件变成 pyc 文件后,加载的速度有所提高,而且 pyc 还是一种跨平台的字节码,由 python 的虚拟机来执行的,就类似于JAVA或者.NET的虚拟机的概念。pyc 的内容与 python 的版本是相关的,不同版本编译后的 pyc 文件是不同的,例如 3.7 版本编译的是pyc文件,而 3.8 版本编译的 python 是无法执行的。

因为 py 文件是可以直接看到源码的,但是一般情况下开发出来的商业软件都不可能直接把源码泄漏出去,这样我们就需要把它编译成 pyc 文件来保护源码。

2.1 引入模块时自动生成 pyc

我们写两个 py 文件用作示例(两个文件在同一文件夹下):

fun.py 文件

def add(a,b):
    return a+b

main.py 文件

from fun import add

a, b = 5, 6
print(add(a, b))

当执行 main 文件时,会自动在当前文件夹下生成 __pycache__ 文件夹,该文件夹包含生成 fun 文件的 pyc 文件。

此时若重命名 fun.cpython-37.pyc 文件为 fun.pyc 后放入与 fun.py 同文件夹下, 再将 fun.py 删除,此时 main.py 仍可正常执行。

可以看到,main.py 是正常执行了的,打开 fun.pyc 文件:

此时已经看不到我们定义的 fun 文件的内容了,因此安全性要更高一些。

2.2 编译单独的 pyc 文件

对于py文件,可以执行下面命令来生成 pyc 文件,转化后的 .pyc 文件将在当前目录的__pycache__文件夹下。

仍以上文中 main.py 和 fun.py 文件为例,操作如下:

1. 使用命令行生成 pyc

python -m compileall <filePath>

之后查看该目录:

此时已正常生成。

2. 使用 python 代码生成 pyc

新建一个 py 文件,输入:

import py_compile

py_compile.compile(r'D:\Words\MyWords\python\源码加密\测试\main.py')

即可得到 main.py 的 pyc 文件。

其中 compile 函数的语法如下:

compile(file[, cfile[, dfile[, doraise]]])

file :表示得是需要编译的py文件的路径

cfile :表示编译后的 pyc 文件名称和路径,默认为直接在 file 文件名后加 c 或者 o,o 表示优化的字节码

dfile:指的是错误消息保存的路径

doraise :有两个值分别是 true 或 false,如果为 true 时则会引发一个 PyCompileError,否则如果编译文件出错,则会有一个错误,默认显示在 sys.stderr 中,而不会引发异常

2.3 批量编译 pyc 文件

一般在项目中我们不会仅仅只编译一个 py 文件,而是需要将整个文件夹下的 py 文件都编译成 pyc 文件,这时我们可以通过以下的方法来实现:

import compileall

compileall.compile_dir(r'D:\Words\MyWords\python\源码加密\测试')

可以看到,该文件夹中的三个 py 文件均被转化成了 pyc 。

其中 compile_dir 函数的语法如下:

compile_dir(dir[, maxlevels[, ddir[, force[, rx[, quiet]]]]])

dir: 表示需要编译的文件夹位置

maxlevels :表示需要递归编译的子目录的层数,默认是 10 层,即默认会把 10 层子目录中的 py 文件编译为 pyc

ddir :表示错误消息保存的路径

force:当为true时表示会被强制编译成 pyc 文件,即使 pyc 文件是最新的依然会被强制编译一次

rx: 表示一个正则表达式,可以排除掉不想要的目录,或者只有符合条件的目录才进行编译

quiet:当为 True 时,在编译后不会再标准输出中来打印信息

3 pyd

pyd的本质是将python转换为 *.c 文件,然后编译为 *.dll/*.so 。以 centos7.6 系统为例:

3.1 Linux 上生成 .so 文件

1. 首先安装 Cpython 库

pip install Cython --install-option="--no-cython-compile"  -i https://pypi.tuna.tsinghua.edu.cn/simple

2. 仍以上文 main.py 和 fun.py 为例, 再创建 getPyd.py 文件

from distutils.core import setup
from Cython.Build import cythonize

setup(name = 'any str...', ext_modules = cythonize("fun.py"))

在命令行输入:

(base) [root@node1 pydtest]# python3 getPyd.py build

得到以下结果:

Compiling fun.py because it changed.
[1/1] Cythonizing fun.py
/opt/soft/anaconda3/lib/python3.8/site-packages/Cython/Compiler/Main.py:369: FutureWarning: Cython directive 'language_level' not set, using 2 for now (Py2). This will change in a later release! File: /home/pbh/work/pydtest/fun.py
  tree = Parsing.p_module(s, pxd, full_module_name)
running build
running build_ext
building 'fun' extension
creating build
creating build/temp.linux-x86_64-3.8
gcc -pthread -B /opt/soft/anaconda3/compiler_compat -Wl,--sysroot=/ -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/opt/soft/anaconda3/include/python3.8 -c fun.c -o build/temp.linux-x86_64-3.8/fun.o
creating build/lib.linux-x86_64-3.8
gcc -pthread -shared -B /opt/soft/anaconda3/compiler_compat -L/opt/soft/anaconda3/lib -Wl,-rpath=/opt/soft/anaconda3/lib -Wl,--no-as-needed -Wl,--sysroot=/ build/temp.linux-x86_64-3.8/fun.o -o build/lib.linux-x86_64-3.8/fun.cpython-38-x86_64-linux-gnu.so

可以看到,在 build/temp.linux-x86_64-3.8/ 文件夹下有 fun.cpython-38-x86_64-linux-gnu.so 文件夹,将其更名 fun.so ,放入 main.py 文件夹下,删掉其余东西,运行 main.py :

(base) [root@node1 pydtest]# cp build/lib.linux-x86_64-3.8/fun.cpython-38-x86_64-linux-gnu.so fun.so
(base) [root@node1 pydtest]# rm -rf build/ fun.c fun.py 
(base) [root@node1 pydtest]# python3 main.py 
11

main.py 已正常执行,且 fun.so 能够很好防止源码泄露。

3.2 windows 上生成 .pyd 文件

1. 安装 6.3.3 版本的 pyarmor

pip install pyarmor==6.3.3 -i https://pypi.tuna.tsinghua.edu.cn/simple

2. 在 fun.py 所在目录使用命令

pyarmor obfuscate --advanced 2 fun.py

此时在该目录下自动生成 dist 文件夹:

fun.py main.py 均已被加密:

这三个文件放在一起,main.py 就能成功运行,甚至可以将 fun.py 转化成 fun.pyc ,只留下 main.py 作程序入口,安全系数极高,缺点是文件变得特别大。

前面提到命令行中 advanced 后接的是数字 2 ,如果选择其他数字,则会有不同的加密模式,读者可以自行尝试:

最后修改:2022 年 08 月 25 日
如果觉得我的文章对你有用,请随意赞赏