Python项目进行加密打包的实践指南

作者:

文章目录
  • 在实际开发中,我们有时需要将 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,所以并不需要更改文件名。
  • 只需一行代码即可加密整个项目: encryptor = Encryptor(“testproject”) # 源目录 encryptor.encryptProject() # 输出到 testproject-dist/ 支持递归处理所有 .py 和 .pyw 文件,并自动创建目标目录结构。
  • 本文实现的 Encryptor 是一个教学级但实用的 Python 项目加密方案。它平衡了易用性与基础保护能力,适合用于: 内部工具分发 客户端脚本保护 防止 casual 代码窥探 虽然不能做到“绝对安全”,但在多数场景下已足够提升逆向成本。更重要的是,通过这个过程,我们深入理解了 Python 的编译、字节码、marshal 序列化等底层机制。 附:注意事项 确保 runtime/ 目录与加密脚本同级; 不要加密 __main__.py 以外的入口文件时破坏 if __name__ == "__main__" 逻辑; 测试加密后项目是否能正常运行! 以上就是Python项目进行加密打包的实践指南的详细内容,更多关于Python项目加密打包的资料请关注风君子博客其它相关文章! 您可能感兴趣的文章: python加密打包程序详解 从源码到Docker全方位解析Python项目打包完整指南 使用PyInstaller打包Python项目过程 python项目打包成docker镜像并运行的完整指南 将Python项目打包成Docker镜像的全过程
  • 目录
    • 引言
    • 整体思路
    • 一、准备工作
    • 二、核心实现解析
      • 1. 密钥生成:动态随机 + SHA3-512
      • 2. 加密流程:字节码 + 异或 + 随机插入密钥
      • 3. 输出加密文件:注入运行时调用
      • 4. 运行时模块(runtime/)
    • 三、使用方式
      • 四、优缺点分析
        • 优点
        • 局限
      • 五、总结

        在实际开发中,我们有时需要将 Python 项目交付给客户或部署到不受控环境中,但又不希望源代码被轻易查看甚至篡改。虽然 Python 作为解释型语言天然难以完全隐藏逻辑,但我们仍可通过编译为字节码 + 自定义加密 + 运行时解密执行的方式,大幅提升逆向分析的门槛。

        本文将基于一个自研的 Encryptor 工具类,分享如何实现一个轻量级、可扩展的 Python 项目加密打包方案。

        我们的目标是:

        1. 保留原有项目结构(包括子目录、.py.pyw 文件);
        2. 将每个 Python 源文件编译为字节码并加密
        3. 生成一个“运行时”模块,用于在运行时动态解密并执行加密后的字节码;
        4. 输出一个独立的加密项目副本,无需原始源码即可运行。

        整个流程如下:

        原始项目 (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,所以并不需要更改文件名。

        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 哈希确保密钥分布均匀且不可预测;
        • 注意:密钥不会保存在外部,而是直接嵌入加密数据中(见下文)。

        安全提示:此设计便于单文件自包含,但若攻击者能提取密钥,则可解密。适用于防普通用户查看,非高强度安全场景。

        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)  # 尾部记录偏移
        

        步骤分解:

        1. 编译源码为 CodeObjectcompile()
        2. 序列化为字节码marshal.dumps()(Python 内部格式)
        3. 用密钥异或加密(简单但有效,避免明文字节码)
        4. 将密钥随机插入加密数据中间(增加静态分析难度)
        5. 在末尾写入 4 字节偏移量(小端无符号整数),用于运行时定位密钥位置

        这种“密钥内嵌 + 偏移记录”的设计,使得每个加密文件都是自包含的,无需额外配置。

        加密后的 .py 文件内容变为:

        from __runtime__ import __run__
        __run__(b'x12xaf...', globals())
        

        其中 b'x12xaf...' 是加密后的字节数据(包含密钥和偏移)。
        当 Python 执行该文件时,会调用 __runtime__.__run__ 来解密并执行原始逻辑。

        虽然本文未展示 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项目加密打包的资料请关注风君子博客其它相关文章!

        您可能感兴趣的文章:

        • python加密打包程序详解
        • 从源码到Docker全方位解析Python项目打包完整指南
        • 使用PyInstaller打包Python项目过程
        • python项目打包成docker镜像并运行的完整指南
        • 将Python项目打包成Docker镜像的全过程

        站内搜索