Python代码加密方案汇总

需求描述

如何保护 python 的源代码?
举个栗子:张三将自己的编写的一个python文件发给了李四,张三希望李四能够正常使用这个文件(可以直接通过标准python解释器执行,或者可以被其他python文件调用),但张三不希望李四看到这个python文件的源代码

在阅读本文讨论的源代码加密之前,有以下内容需要注意:

  • Python是FLOSS(自由/开放源码软件)之一,因此加密python的源代码不符合FLOSS的理念
  • 不存在任何技术手段可以完全阻止用户阅读你的代码,但可以通过技术手段使得用户获得可阅读代码的成本更高
  • 本文所讨论的代码加密仅仅是一个简单的保护机制,但如果遇到顶级黑客,或许他们甚至会访问内存来查看源码
  • 使用法律、商业手段会比技术手段更加有效,例如软件使用许可证SaaS

基础:Python文件格式

为了理解python代码加密的基本原理,首先来了解下python的文件格式。

.py python源代码

这个大家都知道,不做过多阐述。这里着重介绍下Python代码的执行。

Python 代码的执行过程和 Java 类似:

  1. .py文件编译得到 Python 的字节码
  2. Python 虚拟机(Python Virtual Machine) 执行编译好的字节码

Python 虚拟机和 Java 的 JVM 类似,但 Python虚拟机的抽象化程度更高(但不是性能更强)

.pyc 编译得到的字节码文件

  • 在首次导入一个python库的时候,为了让以后再次导入更方便也更快,python会构建一个包含该库的字节码*.pyc文件

    你可以在你的库文件的__pycache__文件夹下看到每个.py文件对应的.pyc文件

.pyo 编译优化后得到的字节码文件

  • Python 3.5之前*.pyc文件通过优化器(-O)创建的文件,可以略微提升加载速度

  • Python 3.5 之后已经取消了.pyo文件的概念,优化后的文件也会以.pyc文件存储

  • .pyc.pyo文件中读取程序比从.py文件中读取得更快,但只是优化了加载速度,不会优化运行速度

.pyd 可被Python调用的Windows DLL文件

  • 基本的Windows DLL文件,是Python的动态链接库
  • 并非从python代码生成,而是其他语言写成的可以被Python调用的Windows DLL文件,例如C++
  • 关于Windows DLL文件的介绍可以参看 Microsoft-什么是DLL?

待加密代码

接下来以实例展示各种代码隐藏的方式。
待加密的原始代码如下:

# -*- coding: UTF-8 –*-
# filename: ergou_test.py
import datetime

class Today():
    def get_time(self):
        print(datetime.datetime.now())
        
    def say(self):
        print("hello from Ergou!")

if __name__ == '__main__':    
    t=Today()
    t.say()
    t.get_time()

直接运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ow9vojcX-1598969435142)(C:\Users\Magfin\AppData\Roaming\Typora\typora-user-images\image-20200722154551869.png)]

基础加密:.pyc加密

加密难度:⭐️
安全性:⭐️

最基础的加密方法是利用 Python 自带的编译器将源代码文件.py 编译得到的二进制的字节码文件.pyc
二进制的字节码文件对于初学者而言有一定的代码隐藏作用,但也只能简单隐藏。
在命令行中输入如下代码可以得到.pyc文件:

python -m py_compile ergou_test.py

.\__pycache__目录下可以看到字节码文件ergou_test.cpython-38.pyc

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d9xAB0BE-1598969435148)(C:\Users\Magfin\AppData\Roaming\Typora\typora-user-images\image-20200722160307492.png)]

字节码文件ergou_test.cpython-38.pyc可以在别的Python文件中导入使用,也可以直接执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J3R8dC8u-1598969435155)(C:\Users\Magfin\AppData\Roaming\Typora\typora-user-images\image-20200722160926582.png)]

如果要对多个文件或整个项目进行编译,可以使用Python标准库中的compileall库。

但是字节码文件极易被反编译得到源代码。

推荐一个反编译库uncompyle,可以直接使用 Pip 安装:

pip install uncompyle

在命令行中运行反编译命令:

uncompyle6 ergou_test.cpython-38.pyc > ergou_test.py

反编译得到的ergou_test.py文件如下:

# uncompyle6 version 3.7.2
# Python bytecode 3.8 (3413)
# Decompiled from: Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:20:19) [MSC v.1925 32 bit (Intel)]
# Embedded file name: ergou_test.py
# Compiled at: 2020-07-22 11:41:54
# Size of source mod 2**32: 273 bytes
import datetime

class Today:

    def get_time(self):
        print(datetime.datetime.now())

    def say(self):
        print('hello from Ergou!')


if __name__ == '__main__':
    t = Today()
    t.say()
    t.get_time()
# okay decompiling ergou_test.cpython-38.pyc

可以看到和原始代码基本没有区别,所以.pyc的加密方式基本相当于裸奔。

进阶加密1:代码混淆

对于代码加密,我们可以换个思路:我们可以暴露代码,但是只要你看不懂我的代码,也就无法使用我的代码了。

代码混淆可以使用pyminifier库,

安装:

pip install pyminifier

使用:

pyminifier --nonlatin --replacement-length=10 -O ergou_test.py

对于单个文件,会直接输出混淆后的代码:

import datetime
鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈܙ=print
鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈ堛=datetime.now
鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈ𨱕=datetime.datetime
class 鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈ𑰟():
 def 鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈﮕ(self):
  鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈܙ(鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈ𨱕.now())
 def 鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈ𐠗(self):
  鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈܙ("hello from Ergou!")
if __name__=='__main__':
 鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈ䱺=鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈ𑰟()
 鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈ䱺.鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈ𐠗()
 鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈ䱺.鄮롍ﹹݾ𐼖氉ﵫ𨐟ࡈﮕ()
# Created by pyminifier (https://github.com/liftoff/pyminifier)

这样混淆的代码可读性变得极差,然而即便如此,还是不难看出代码内部中的逻辑,通过变量名替换等方法还是可以看出其大致的逻辑。

pyminifier中还有另外一种代码混淆方法,利用Base64对代码进行再次编码,再利用lzma算法进行压缩,但是这种加密方式仅仅用于代码没有调用隐式导入的情况。
使用:

pyminifier --lzma "ergou_test.py"

结果:

import lzma, base64
exec(lzma.decompress(base64.b64decode('/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AC9AIddADSbSme4Ujxz0DHnfZG4YVh3r9CsdtAwW4DRCnyvCgYFNNvit5ucVyZEXm0xrZQFnMmnv5z9aXgGq8oGWLMz+nFaI+A7zI5M115jvtlkHe2PTQ44cNNJgVhXoX718yXUd9RQuI13Z9g+nUZiG4oGdJRmK7JehLK/UQ2Tic8JFOCKT4lM8+hv4AAAZj5170QAhWgAAaMBvgEAALPN0p2xxGf7AgAAAAAEWVo=')))
# Created by pyminifier (https://github.com/liftoff/pyminifier)

这样得到的代码完全看不出原来的逻辑,但是Base64非常容易被反编译,因此加密效果还是有限的。

可以考虑将上诉两者混淆方法结合起来,这样可以进一步增大代码的混淆程度。

进阶加密2:.pyd/.so加密

加密难度:⭐️ ⭐️ ⭐️
安全性:⭐️ ⭐️ ⭐️

Cython是一个编程语言,它通过类似Python的语法来编写C扩展并可以被Python调用。能够将Python+C混合编码的.pyx脚本转换为C代码,主要用于优化Python脚本性能或Python调用C函数库。基于它的原理,我们可以得到一种代码加密的思路:将 .py/.pyx 编译为 .c 文件,再将 .c 文件编译为 .so(Unix) 或 .pyd(Windows),这样得到的文件更难反编译。

注意:Windows环境下使用该库可能需要配置Microsoft Visual C++相关库

推荐一个库jmpy3,该库能够一键完成上诉流程。

安装:

pip install jmpy3

使用:

jmpy -i "ergou_test.py" -m 0

.\dist文件夹下得到ergou_test.pyd文件

可以在Python文件中导入ergou_test.pyd,并使用其中的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZxKDNCgj-1598969435161)(C:\Users\Magfin\AppData\Roaming\Typora\typora-user-images\image-20200722170908630.png)]

关于这种加密方法的安全性,笔者目前尚未发现能将.so/.pyd文件直接反编译为.py的方法,因此较为可靠。

成熟的解决方案:Pyarmor

加密难度:⭐️ ⭐️⭐️
安全性:⭐️ ⭐️ ⭐️ ⭐️⭐️

经过多番搜索,笔者找到了一个比较完善的Python脚本加密方案:pyarmor

pyarmor能够加密 Python 脚本,同时还具备以下功能:

  • 设置加密脚本的有效期限
  • 绑定加密脚本到硬盘、网卡等硬件设备

Pyarmor加密原理比较复杂,在此不做阐述,感兴趣的可以直接查看他的官方文档。Pyarmor的开发者为中国人,因此文档也是中文,查看起来还是比较方便的。

特别注意

Pyarmor是一个共享软件,试用版免费但存在功能限制(主要是加密代码不能超过 32 MB),完整版的授权价格为 286.00 人民币。

详细的软件许可说明可以参看Pyarmor软件许可

接下来展示pyarmor的使用。

Pyarmor 的基础使用

安装:

pip install pyarmor

基础使用 加密代码:

pyarmor obfuscate ergou_test.py

在输出目录.\dist文件夹下可以看到加密后的脚本ergou_test.py,代码内容如下:

from pytransform import pyarmor_runtime
pyarmor_runtime()
__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x08\x00\x55\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\xc0\x10\x00\x00\x00\x00\x00\x18\x4d\x8f\xde\x78\xa2\x8e\xb4\x57\xd4\x7f\xbd\x06\x57\x35\x4b\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x20\x87\x36\x3e\xec\x5e\xa8\x81\x31\x30\x52\xa7\x6b\xf0\x82\xde\x45\x9e\x37\x7f\xde\x9e\xec\x04\xfa\xec\x6c\x5b\x09\x0b\x68\x05\x03\x72\xdd\x6a\x82\xff\x0e\x14\x13\x41\xa3\x6f\x22\xf0\x00\x96\xd1\xe9\xc6\xd8\x7d\x9d\xa6\x8b\xf5\xa3\x7a\x35\x52\xed\x05\x15\xa4\x2c\xdb\x10\xe5\x9c\xc0\xfc\x38\x11\x59\xd2\x26\x4f\xb5\x3f\xba\x10\xad\xe4\x26\x67\xac\x64\x15\xce\x6e\x84\x90\xeb\x8b\x1f\xb9\xbf\xc0\xbb\x7f\xa7\x8c【这里省略了一段很长很长很长的十六进制数】\xb5\x3f\xba', 2)

除了加密脚本之外,额外的那个目录 pytransform 叫做 运行辅助包 ,它是运行加密脚本不可缺少的。

脚本可以直接运行:

python ergou_test.py

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SVSECAb4-1598969435165)(C:\Users\Magfin\AppData\Roaming\Typora\typora-user-images\image-20200723174239163.png)]

也可以导入到别的脚本中:

在这里插入图片描述

如果需要分享代码的话,需要将.\dist目录下的全部文件都发送过去,对方无需安装Pyarmor库。

Pyarmor 的进阶使用

加密脚本的同时会在输出目录下面生成一个默认许可文件 dist/license.lic ,它 允许加密脚本运行在任何设备上并且永不过期。

可以根据需求生成指定有效日期、主机MAC地址、硬盘编号甚至是Docker容器ID的许可文件。

生成有效日期到 2020-07-22 的许可文件:、

pyarmor licenses --expired 2020-07-22 code-001

执行这条命令 Pyarmor 会生成一个带有效期的认证文件:

  • 创建 license.lic ,保存在 licenses/code-001
  • 创建 license.lic.txt ,保存在 licenses/code-001

然后将许可文件license.lic文件复制到.\dist\pytransform目录下,替换原来的license.lic

这样,加密脚本在2020年7月22日之后就无法在运行了,比如现在就无法执行了。

censes --expired 2020-07-22 code-001

执行这条命令 Pyarmor 会生成一个带有效期的认证文件:

  • 创建 license.lic ,保存在 licenses/code-001
  • 创建 license.lic.txt ,保存在 licenses/code-001

然后将许可文件license.lic文件复制到.\dist\pytransform目录下,替换原来的license.lic

这样,加密脚本在2020年7月22日之后就无法在运行了,比如现在就无法执行了。

高级加密:定制Python解释器

未完待续…


如有帮助,欢迎点赞/转载~
(听说给文章点赞的人代码bug特别少👀)
联系邮箱:mrjingcheng@foxmail.com
个人公众号:禅与电脑维修艺术
欢迎关注公众号,也欢迎通过邮箱交流。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐