如何保护 python 的源代码?
举个栗子:张三将自己的编写的一个python文件发给了李四,张三希望李四能够正常使用这个文件(可以直接通过标准python解释器执行,或者可以被其他python文件调用),但张三不希望李四看到这个python文件的源代码。
在阅读本文讨论的源代码加密之前,有以下内容需要注意:
为了理解python代码加密的基本原理,首先来了解下python的文件格式。
.py
python源代码这个大家都知道,不做过多阐述。这里着重介绍下Python代码的执行。
Python 代码的执行过程和 Java 类似:
.py
文件编译得到 Python 的字节码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文件接下来以实例展示各种代码隐藏的方式。
待加密的原始代码如下:
# -*- 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()
直接运行结果如下:
.pyc
加密加密难度:⭐️
安全性:⭐️
最基础的加密方法是利用 Python 自带的编译器将源代码文件.py
编译得到的二进制的字节码文件.pyc
。
二进制的字节码文件对于初学者而言有一定的代码隐藏作用,但也只能简单隐藏。
在命令行中输入如下代码可以得到.pyc
文件:
python -m py_compile ergou_test.py
在.\__pycache__
目录下可以看到字节码文件ergou_test.cpython-38.pyc
字节码文件ergou_test.cpython-38.pyc
可以在别的Python文件中导入使用,也可以直接执行。
如果要对多个文件或整个项目进行编译,可以使用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
的加密方式基本相当于裸奔。
对于代码加密,我们可以换个思路:我们可以暴露代码,但是只要你看不懂我的代码,也就无法使用我的代码了。
代码混淆可以使用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非常容易被反编译,因此加密效果还是有限的。
可以考虑将上诉两者混淆方法结合起来,这样可以进一步增大代码的混淆程度。
.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
,并使用其中的方法
关于这种加密方法的安全性,笔者目前尚未发现能将.so
/.pyd
文件直接反编译为.py
的方法,因此较为可靠。
加密难度:⭐️ ⭐️⭐️
安全性:⭐️ ⭐️ ⭐️ ⭐️⭐️
经过多番搜索,笔者找到了一个比较完善的Python脚本加密方案:pyarmor
pyarmor
能够加密 Python 脚本,同时还具备以下功能:
Pyarmor
的加密原理比较复杂,在此不做阐述,感兴趣的可以直接查看他的官方文档。Pyarmor
的开发者为中国人,因此文档也是中文,查看起来还是比较方便的。
特别注意
Pyarmor
是一个共享软件,试用版免费但存在功能限制(主要是加密代码不能超过 32 MB),完整版的授权价格为 286.00 人民币。详细的软件许可说明可以参看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
也可以导入到别的脚本中:
如果需要分享代码的话,需要将.\dist
目录下的全部文件都发送过去,对方无需安装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日之后就无法在运行了,比如现在就无法执行了。
未完待续…
如有帮助,欢迎点赞/转载~
(听说给文章点赞的人代码bug特别少)
联系邮箱:[email protected]
个人公众号:禅与电脑维修艺术
欢迎关注公众号,也欢迎通过邮箱交流。