引言
在实际开发中,我们有时需要将 python 项目交付给客户或部署到不受控环境中,但又不希望源代码被轻易查看甚至篡改。虽然 python 作为解释型语言天然难以完全隐藏逻辑,但我们仍可通过编译为字节码 + 自定义加密 + 运行时解密执行的方式,大幅提升逆向分析的门槛。
本文将基于一个自研的 encryptor 工具类,分享如何实现一个轻量级、可扩展的 python 项目加密打包方案。

整体思路
我们的目标是:
- 保留原有项目结构(包括子目录、
.py和.pyw文件); - 将每个 python 源文件编译为字节码并加密;
- 生成一个“运行时”模块,用于在运行时动态解密并执行加密后的字节码;
- 输出一个独立的加密项目副本,无需原始源码即可运行。
整个流程如下:
原始项目 (testproject/) ├── main.py ├── utils/ │ └── helper.py └── ... ↓ encryptor.encryptproject() 加密项目 (testproject-dist/) ├── main.py ← 内容变为: from __runtime__ import __run__; __run__(b'...', globals()) ├── utils/ │ └── helper.py ← 同上 └── __runtime__/ ← 包含解密和执行逻辑
一、准备工作
由于我们需要在运行时动态解密并执行加密后的字节码,但是又不想暴露解密算法,那么需要将解密函数单独编译为.pyd或者其他(.so/.dll)机器码文件,这里给出示例:
# runtime.py
def __run__(en_code: bytes, globals_: dict):
import struct
import marshal
def decrypt(en_code: bytes) -> bytes:
en_code, offset_bytes = en_code[:-4], en_code[-4:] # 去除偏移量
insert_offset = struct.unpack("<i", offset_bytes)[0]
key = en_code[insert_offset : insert_offset + 64] # 提取key
en_code = en_code[:insert_offset] + en_code[insert_offset + 64 :] # 去除key
# 解密
de_code = bytes([en_code[i] ^ key[i % len(key)] for i in range(len(en_code))])
return de_code
code = marshal.loads(decrypt(en_code))
exec(code, globals_, globals_)
完成编译后,新建runtime文件夹,将pyd文件(如runtime.cp312-win_amd64.pyd)放入,并新建__init.py__文件,输入一行代码:
from .runtime import __run__
python解释器会根据目前设备自动选择runtime.cp312-win_amd64.pyd,所以并不需要更改文件名。
二、核心实现解析
1. 密钥生成:动态随机 + sha3-512
def __generatekey(self) -> bytes:
key = "".join([chr(randint(33, 126)) for _ in range(64)])
return hashlib.sha3_512(key.encode("utf-8")).digest()
- 每次加密一个文件时,都会独立生成一个 64 字节的随机密钥;
- 使用
sha3_512哈希确保密钥分布均匀且不可预测; - 注意:密钥不会保存在外部,而是直接嵌入加密数据中(见下文)。
安全提示:此设计便于单文件自包含,但若攻击者能提取密钥,则可解密。适用于防普通用户查看,非高强度安全场景。
2. 加密流程:字节码 + 异或 + 随机插入密钥
code = compile(..., "exec")
bytecode = marshal.dumps(code)
en_code = bytes([bytecode[i] ^ key[i % len(key)] for i in range(len(bytecode))])
insert_offset = randint(0, len(en_code) - 1)
en_code = en_code[:insert_offset] + key + en_code[insert_offset:]
en_code += struct.pack("<i", insert_offset) # 尾部记录偏移
步骤分解:
- 编译源码为 codeobject →
compile() - 序列化为字节码 →
marshal.dumps()(python 内部格式) - 用密钥异或加密(简单但有效,避免明文字节码)
- 将密钥随机插入加密数据中间(增加静态分析难度)
- 在末尾写入 4 字节偏移量(小端无符号整数),用于运行时定位密钥位置
这种“密钥内嵌 + 偏移记录”的设计,使得每个加密文件都是自包含的,无需额外配置。
3. 输出加密文件:注入运行时调用
加密后的 .py 文件内容变为:
from __runtime__ import __run__ __run__(b'\x12\xaf...', globals())
其中 b'\x12\xaf...' 是加密后的字节数据(包含密钥和偏移)。
当 python 执行该文件时,会调用 __runtime__.__run__ 来解密并执行原始逻辑。
4. 运行时模块(runtime/)
虽然本文未展示 runtime/__init__.py 的内容,但其核心逻辑大致如下:
def __run__(en_code: bytes, globals_: dict):
import struct
import marshal
def decrypt(en_code: bytes) -> bytes:
en_code, offset_bytes = en_code[:-4], en_code[-4:] # 去除偏移量
insert_offset = struct.unpack("<i", offset_bytes)[0]
key = en_code[insert_offset : insert_offset + 64] # 提取key
en_code = en_code[:insert_offset] + en_code[insert_offset + 64 :] # 去除key
# 解密
de_code = bytes([en_code[i] ^ key[i % len(key)] for i in range(len(en_code))])
return de_code
code = marshal.loads(decrypt(en_code))
exec(code, globals_, globals_)
此模块需随加密项目一同分发,但本身不含敏感逻辑,可公开。
三、使用方式
只需一行代码即可加密整个项目:
encryptor = encryptor("testproject") # 源目录
encryptor.encryptproject() # 输出到 testproject-dist/
支持递归处理所有 .py 和 .pyw 文件,并自动创建目标目录结构。
四、优缺点分析
优点
- 简单有效:无需第三方依赖,仅用标准库;
- 自包含:每个文件独立解密,无全局密钥管理;
- 保留结构:项目组织不变,兼容原有导入路径;
- 防小白:普通用户无法直接阅读源码。
局限
- 非高强度加密:异或加密可被内存 dump 或动态调试破解;
- 性能开销:每次执行需解密,对启动速度有轻微影响;
- 不防反编译:一旦字节码被还原,可用
uncompyle6等工具反编译; - 密钥内嵌风险:熟练攻击者可提取密钥批量解密。
若需更高安全性,建议结合:
- c 扩展封装核心逻辑
- 商业混淆工具(如 pyarmor)
- 网络授权验证
五、总结
本文实现的 encryptor 是一个教学级但实用的 python 项目加密方案。它平衡了易用性与基础保护能力,适合用于:
- 内部工具分发
- 客户端脚本保护
- 防止 casual 代码窥探
虽然不能做到“绝对安全”,但在多数场景下已足够提升逆向成本。更重要的是,通过这个过程,我们深入理解了 python 的编译、字节码、marshal 序列化等底层机制。
附:注意事项
- 确保
runtime/目录与加密脚本同级; - 不要加密
__main__.py以外的入口文件时破坏if __name__ == "__main__"逻辑; - 测试加密后项目是否能正常运行!
以上就是python项目进行加密打包的实践指南的详细内容,更多关于python项目加密打包的资料请关注代码网其它相关文章!
发表评论