Linux下使用Cython保护python代码防止反编译

一、目前已知的一些保护python代码措施

1、发行.pyc文件

使用py_compile模块将py文件转为pyc文件,容易被反编译成py文件,且反编译后的代码几乎和源代码一样。

2、代码混淆

在线代码混淆网站:https://pyob.oxyry.com/

 虽然混淆后的代码乍一眼看上去好像头绪很大看不出来,但是静下来细看也就是改了一些变量和加了一些空格啥的,变量几乎都是O0OO0O0OOOOOOOO0O这种变量名,其中重要的方法逻辑还是能看出来。

3、使用Pyarmor

保护 Python 脚本的工具,能够加密 Python 脚本,保护运行时刻的 Python 代码不被泄露,设置加密脚本的有效期限,绑定加密脚本到硬盘、网卡等硬件设备。

最大缺点:要钱!

4、打包成可执行文件

windows下一般用pyinstaller、py2exe等工具打包,经测试,如果依赖包本身就比较大,比如含torch等,打包后的exe文件也会比较大。

网上还有一款打包工具Nuitka,可以打包win和Linux,具体用法不作介绍,有需要可以自行了解。

5、使用Cython打包成二进制文件(推荐)

将.py编译为.c文件,再将.c文件编译为.so(Linux)或者.pyd(Windows),运行时可以被python解释器识别。

缺点:听说不支持一小部分代码,目前还未碰到;环境改变需要重新编译。

二、Linux下使用Cython

1、安装Cython

pip install Cython

我测试的版本为 0.29.34 。

2、编写python脚本执行编译

compile.py

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

import os
import shutil

    
# 需要加密的文件列表
encrypt_py_files = ['demo/demo_xxxx.py','utils/objdetector.py']

for encrypt_py_file in encrypt_py_files:
    # 获取文件名,不带后缀
    filename_without_extension  = os.path.basename(encrypt_py_file).replace('.py','')
    # 转成so文件
    setup(ext_modules=cythonize(encrypt_py_file,language_level = "3"))
    # 删除生成的c文件
    os.remove(encrypt_py_file.replace('.py','.c'))
    # 删除原py文件,注意备份
    # os.remove(encrypt_py_file)
    # 去build文件夹寻找对应的so文件
    for root, dirs, files in os.walk('build'):
        for f in files:
            file_path = os.path.join(root, f)
            # 如果是so文件并且文件名和需加密的py文件名一致
            if f.endswith('so') and f.split('.')[0] == filename_without_extension:
                # 将so文件移动到原py文件目录下并重命名
                shutil.move(file_path, encrypt_py_file.replace('.py','.so'))
            else:
                # 其他文件一律删除
                os.remove(file_path)
                

可以将该python脚本放在项目的最外层目录下,我已经按照需要编写好了处理逻辑。

简单解释下:执行setup后,会在当前目录创建一个build目录,里面的lib开头的文件夹下会存放编译好的so文件,需要将so文件移动至原py文件的目录下,并且文件名称需要和py文件名称一致,此外还会生成一些.c和.o文件,统统删掉。

2024.01.11补充:

有些Linux环境下,可能会产生编译信息:

Compiling multi_rtsp_detect.py because it changed.
[1/1] Cythonizing demo_xxxx.py
/home/ubuntu/.conda/envs/pytorch/lib/python3.8/site-packages/setuptools/dist.py:771: UserWarning: Usage of dash-separated 'description-file' will not be supported in future versions. Please use the underscore name 'description_file' instead
  warnings.warn(
/home/ubuntu/.conda/envs/pytorch/lib/python3.8/site-packages/setuptools/config/setupcfg.py:508: SetuptoolsDeprecationWarning: The license_file parameter is deprecated, use license_files instead.
  warnings.warn(msg, warning_class)
running build_ext
building 'demo_xxxx' extension
gcc -pthread -B /home/ubuntu/.conda/envs/pytorch/compiler_compat -Wl,--sysroot=/ -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/ubuntu/.conda/envs/pytorch/include/python3.8 -c demo_xxxx.c -o build/temp.linux-x86_64-cpython-38/demo_xxxx.o
gcc -pthread -shared -B /home/ubuntu/.conda/envs/pytorch/compiler_compat -L/home/ubuntu/.conda/envs/pytorch/lib -Wl,-rpath=/home/ubuntu/.conda/envs/pytorch/lib -Wl,--no-as-needed -Wl,--sysroot=/ build/temp.linux-x86_64-cpython-38/demo_xxxx.o -o build/lib.linux-x86_64-cpython-38/demo_xxxx.cpython-38-x86_64-linux-gnu.so
copying build/lib.linux-x86_64-cpython-38/demo_xxxx.cpython-38-x86_64-linux-gnu.so ->

已经自动把so文件拷贝到原py文件目录,但是名称没改。

3、执行compile.py

python compile.py build_ext --inplace

4、运行代码

1)直接在终端运行:

python -c "from demo.demo_xxxx import main;main()"

2)使用python文件运行:

run.py

from demo_xxxx import main

main()
python demo/run.py

三、可能遇到的错误

错误1:

ImportError: dynamic module does not define module export function (PyInit_demo_xxxx)

原因:so文件的文件名跟py文件名不一致


错误2:

SyntaxError: Non-UTF-8 code starting with '\xcd' in file demo/demo_xxxx.so on line 2, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

原因:使用了命令 python demo/demo_xxxx.so 来运行so文件会导致该错误,应选择正确的运行方式。


2024.01.11补充:

错误3:

其实也不算Cython错误,顺便记录下。如果保护的python文件中有用到多进程方法,则在启动该python文件方法时,需要在main()方法内启动。

错误包含信息:

RuntimeError:
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

在main方法下启动:

if __name__ == '__main__':
    方法名()

你可能感兴趣的:(python,python,开发语言)