Python 工程加密方案探索

1. 背景

随着 AI 技术的发展和普及,越来越多的企业开始使用 Python 进行项目开发。Python 以其简洁的语法,强大的库支持,以及优秀的可读性,受到了广大开发者的喜爱。然而,Python作为一种解释型语言,其源代码是公开可读的,这在一定程度上增加了代码被非法篡改或窃取的风险。

当企业将 Python 项目部署到客户服务器后,由于源代码的可读性,客户有可能获取到项目的完整源代码,这对企业的技术秘密保护构成了巨大的挑战。此外,如果客户在没有得到授权的情况下对源代码进行修改,还可能导致项目运行出现问题,影响服务质量。

那么,如何保护 Python 项目的源代码,防止在部署到客户服务器后被客户获取呢?

针对这一问题,本文将对 Python 项目的加密方案进行技术调研,希望通过对现有的技术方案的分析和比较,为企业选择合适的源代码保护方案提供参考。

本文将首先介绍 Python 源代码的保护原理和方法,然后深入探讨各种源代码保护技术的优缺点,最后根据企业的具体需求,提出适合的源代码保护方案。

2. 方案介绍

目前业界存在以下4种常见的软件解决方案:

方案 优点 缺点
编译成字节码 (.pyc文件) - 提供基本的代码保护,防止直接阅读源码。 - Python自身就支持生成.pyc文件,操作简单。 - 字节码可以被反编译,安全性相对较低。 - 不会隐藏程序的逻辑结构和算法。
源代码混淆 - 使代码难以阅读和理解,增加了代码保护的复杂性。 - 可以结合其他方法使用,如编译成.pyc后再混淆。 - 混淆后的代码可能会影响运行效率。 - 完全的安全性仍无法保证,有可能被专业人士破解。
打包为可执行文件 - 用户只能看到一个可执行文件,而不是一系列的Python源文件。 - 适合分发给最终用户使用,使用起来更加简单直接。 - 打包后的应用体积相对较大。 - 有可能被专业工具分析和提取原始字节码。
Cython 或 C 扩展 - 将Python代码转换成C代码,从而编译成机器码执行,提高了保护级别和执行效率。 - 可以与Python无缝集成,部分代码转换成C后,其余代码仍然可以使用Python编写。 - 需要C语言的知识,增加了开发的复杂性。 - 不是所有Python代码都适合或需要转换成C。

由于笔者不是太擅长 C 语言,而且将项目转换成 C 代码,成本较大,感兴趣的朋友可以自行研究,本文主要介绍前面三种方式。 项目 demo 源码地址:github.com/yugasun/pyp...

2.1 编译成字节码(.pyc文件)

克隆项目后,git checkout 分支 pycompile

在项目根目录下添加 compile.py 文件,内容如下:

python 复制代码
import compileall
import re

exclude_dir_pattern = re.compile(r"[\\/](venv|build|dist)[\\/]")

compileall.compile_dir("./", force=True, rx=exclude_dir_pattern)

然后执行该文件:

bash 复制代码
python compile.py

执行成功后,就会在项目根目录下生成文件夹 __pycache__,内容如下:

bash 复制代码
tree __pycache__ 
__pycache__
├── compile.cpython-310.pyc
└── main.cpython-310.pyc

之后只需要执行命令 PYTHONPATH=__pycache__ python -m main 即可。

2.2 打包为可执行文件 - pyinstaller

官方简介: PyInstaller 读取您编写的 Python 脚本。它分析您的代码,以发现您的脚本执行所需的所有其他模块和库。然后,它会收集所有这些文件的副本,包括活动的 Python 解释器!-- 并将它们与您的脚本放在一个文件夹中,或者可以选择放在一个可执行文件中。

通过定义编译脚本,pyinstaller 可以将 python 脚本编译生成可执行文件,使最终用户不需要安装Python环境就可以运行程序。这在一定程度上隐藏了源代码。

这里以生成可执行文件为例:

2.2.1 定义 pyinstaller 打包需要的 spec 文件

pyprotect.spec 内容如下:

python 复制代码
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_submodules
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
options = parser.parse_args()

include_libraries = [
    "fastapi",
    "uvicorn",
]

hidden_imports = []

for module in include_libraries:
    hidden_imports += collect_submodules(module)

project_name = "pyprotect"
project_version = "0.1.0"

a = Analysis(
    ["main.py"],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=hidden_imports,
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

if options.debug:
    print("####### Debug mode #######")
    exe = EXE(
        pyz,
        a.scripts,
        a.binaries,
        a.zipfiles,
        a.datas,
        name=project_name,
        debug=True,
        strip=False,
        upx=False,
        console=True,
    )
else:
    exe = EXE(
        pyz,
        a.scripts,
        a.binaries,
        a.datas,
        [],
        name=project_name + "_" + project_version,
        debug=False,
        bootloader_ignore_signals=False,
        strip=False,
        upx=True,
        upx_exclude=[],
        runtime_tmpdir=None,
        console=True,
        disable_windowed_traceback=False,
        argv_emulation=False,
        target_arch=None,
        codesign_identity=None,
        entitlements_file=None,
        icon=["ico\\example.ico"],
    )

2.2.2 执行构建命令

项目根目录下添加 gen.sh 文件,内容如下:

bash 复制代码
#!/bin/sh

rm -rf ./dist

# get --debug option
if [ "$1" = "--debug" ]; then
  DEBUG="--debug"
else
  DEBUG=""
fi

# Pyinstaller options
PYINSTALLER="pyinstaller -y"
SPEC_FILE="pyprotect.spec"

${PYINSTALLER} ${SPEC_FILE} -- ${DEBUG}

上面的 --debug 参数并不是 pyinstaller 命令参数,我这里只是为了调试,而自定义的。

通过定义项目入口文件 main.py ,执行 pyinstaller 命令后,就可以在项目根目录下生成可执行文件 dist/pyprotect_0.1.0,执行 ./dist/pyprotect_0.1.0 就可以启动项目了。

当然以上脚本还定义了一些额外参数,这里就不一一介绍,可以参考官方文档:pyinstaller.org/en/stable/u...

2.3 代码混淆 - pyarmor

克隆项目后,git checkout 分支 pyarmor
注意:请在具有相同 Python 版本和相同平台的机器中运行这个混淆,否则它不起作用。因为pyarmor_runtime_000000 有一个扩展模块(用C或C++编写的一个模块,使用 Python 的 CAPI 与核心和用户代码进行交互),它依赖于平台并绑定到Python版本。

安装 pyarmor:

bash 复制代码
pip install pyarmor

2.3.1 混淆单一文件

bash 复制代码
pyarmor gen demo.py

此命令生成一个混淆脚本 dist/demo.py,这是一个有效的Python脚本,通过Python解释器可以运行它:

bash 复制代码
python dist/demo.py

检查默认输出路径中的所有生成文件:

bash 复制代码
ls dist/
...    demo.py...    pyarmor_runtime_000000

有一个额外的Python包 pyarmor_runtime_000000,这是运行混淆脚本所必需的。

2.3.2 混淆包/文件夹内

它还可以将整个路径 dist 复制到另一台机器上。但这并不方便,更好的方法是使用-i在包路径中生成所有必需的文件:

bash 复制代码
pyarmor gen -O dist app

检查输出:

bash 复制代码
tree dist
dist
├── app
│   ├── __init__.py
│   ├── libs
│   │   ├── __init__.py
│   │   └── moduleA.py
│   └── main.py
├── demo.py
└── pyarmor_runtime_000000
    ├── __init__.py
    ├── __pycache__
    │   └── __init__.cpython-311.pyc
    └── pyarmor_runtime.so

现在所有内容都在包路径 dist 中,只需将整个路径复制到任何目标机器即可。

2.3.3 支持过期时间的混淆

可以很容易地通过 -e 来设置混淆脚本的到期日期。例如,生成过期日期为 10 天的混淆脚本:

bash 复制代码
pyarmor gen -O dist -e 10 demo.py

运行混淆脚本 dist/demo.py 以验证它:

bash 复制代码
python dist/demo.py

让我们使用另一个表单来设置过去的日期 2023-05-30

bash 复制代码
pyarmor gen -O dist -e 2023-05-30 demo.py

现在 dist/demo.py 应该不起作用:

bash 复制代码
python dist/demo.py

通过给混淆后的代码设置过期时间,可以有效控制目标机器的使用权限,可以用作基于时间的 license 机制。

2.3.4 将混淆脚本绑定到设备

从 Pyarmor 8.4.6 开始,通过 python -m pyarmor.cli.hdinfo 获取目标机器硬件信息:

bash 复制代码
python -m pyarmor.cli.hdinfo

Machine ID: 'abcedkldjkldjdkldjkd'
Default Mac address: '9c:3e:53:7f:44:5a'
Default IPv4 address: '192.168.0.46'

然后可以用 -b 将硬件信息绑定到混淆脚本。例如,绑定到 Mac 地址:

bash 复制代码
pyarmor gen -O dist -b 9c:3e:53:7f:44:5a demo.py

所以 dist/demo.py 只能在目标机器中运行。

3. 总结

在对比了本文提到的几种方案之后,我个人更倾向于推荐使用 打包为可执行文件 的方法来有效防止源代码泄露。采用这种方式,当应用部署后,用户将只能接触到一个可执行文件,而不是众多散落的 Python 源文件。这不仅能在一定程度上保护代码不被轻易获取,还因其直接性和易用性,特别适合向最终用户分发。而且也可以根据实际需求,定制化 spec 文件,来优化打包后的可执行文件。

当然,实际情况因人而异,不同的项目可能会有不同的需求,比如需要更高的安全性,或者需要更好的运行效率。在选择源代码保护方案时,需要根据项目的具体情况,综合考虑各种因素,选择最适合的方案。

4. 项目源码

github.com/yugasun/pyp...

5. 参考文档

  1. python compileall
  2. pyinstaller
  3. pyarmor
相关推荐
努力的家伙是不讨厌的3 分钟前
解析json导出csv或者直接入库
开发语言·python·json
云空26 分钟前
《Python 与 SQLite:强大的数据库组合》
数据库·python·sqlite
newxtc33 分钟前
【国内中间件厂商排名及四大中间件对比分析】
安全·web安全·网络安全·中间件·行为验证·国产中间件
凤枭香1 小时前
Python OpenCV 傅里叶变换
开发语言·图像处理·python·opencv
测试杂货铺1 小时前
外包干了2年,快要废了。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
艾派森1 小时前
大数据分析案例-基于随机森林算法的智能手机价格预测模型
人工智能·python·随机森林·机器学习·数据挖掘
weixin_442643422 小时前
推荐FileLink数据跨网摆渡系统 — 安全、高效的数据传输解决方案
服务器·网络·安全·filelink数据摆渡系统
小码的头发丝、2 小时前
Django中ListView 和 DetailView类的区别
数据库·python·django
星尘安全2 小时前
安全工程师入侵加密货币交易所获罪
安全·区块链·漏洞·加密货币
Chef_Chen3 小时前
从0开始机器学习--Day17--神经网络反向传播作业
python·神经网络·机器学习