Python代码可以加密吗?Python字节码告诉你
off999 2024-09-23 11:27 46 浏览 0 评论
众所周知,执行Python程序可以直接使用python.exe命令,如下所示:
python abc.py
看到python直接执行了abc.py,可能很多同学认为python是解释执行abc.py的,其实不然。如果要真是解释执行,那效率慢的就没法用了。实际上,Python与Java一样,也是玩字节码出身。Java的字节码叫Java ByteCode,Python的字节码叫Python ByteCode。Python在第一次运行abc.py文件时,会将源代码文件编译成字节码,然后再执行。当然,还可以选择直接生成字节码文件(扩展名是pyc),然后直接执行Python字节码文件。
通常Python是以源代码形式发布的,不过对于一些敏感信息,不希望以源代码形式发布,就可以用字节码形式发布。当然,字节码也可以被反编译。为了让Python源代码更安全,可以制作自己的私有Python环境,这些内容我们后面再说。
相信很多没接触过过Python字节码的同学一定有很多疑问,那么就继续看后面的内容吧!
1. 如何查看Python字节码
我们首先来查看一下Python的字节码,以证明在运行Python脚本时确实是先将Python代码编译成字节码,然后执行的是字节码,而不是直接执行Python源代码。
先看下面的代码:
在这段代码中有一个fun函数,里面使用了全局变量value和局部变量name,并输出了这两个变量的值。最后导入了dis模块。在该模块中有一个disassemble函数,用于输出任何包含__code__属性的Python代码段的字节码形式。
现在执行这段代码,会输出如下内容:
很明显,disassemble输出了类似汇编代码的东西。其实这就是Python字节码的可读形式。每一条指令对应一个字节码。那么为什么要查看字节码呢?其实对于应用开发者来说,最直接的作用就是更好地理解Python源代码。
例如,本例使用了全局变量,也就是global关键字,那么global关键字到底代表什么呢?从Python字节码中就可以很容易看出端倪。
在Python源代码中发生了2次赋值,代码如下:
其中value是全局变量,name是fun函数的局部变量。将这两条赋值操作转换为Python字节码,会得到如下的代码:
从Python字节码可以看出,每一条赋值语句转换成了2条Python字节码。其中都使用了LOAD_CONST指令,这是装载常量的指令。因为value和name都被赋予了一个常量,只是一个是整数,另一个是字符串。不过由于Python在使用变量时不需要指定变量类型(变量有类型,但不需要在定义变量时指定,使用变量时再确定变量的类型),所以不管是装载什么类型的常量给变量赋值,都使用LOAD_CONST指令。
但第2条指令就不同了,对于全局变量value,使用STORE_GLOBAL指令将常量赋给变量,而局部变量name,使用了STORE_FAST指令将常量赋给了变量。这两条指令的区别就是存储的位置不同。由于Python将全局变量和变量放到了不同的位置,所以这两条指令会分别将常量值保存到这些位置。
从这一点判断,global value这条语句其实并没有执行,他只是一个开关,如果加上global value,当为value赋值时就使用STORE_GLOBAL指令,如果没有global value,当为value赋值时就使用STORE_FAST指令。
如果除了global value外,其他的代码都去掉,就看不到global value的身影了。
看下面的Python代码:
执行这段代码,只会得到下面2条Python字节码:
这2条Python字节码实际是让fun函数有一个默认的返回值,也就是如果函数不显式返回一个值,那么默认就会返回None。这里面并没有看到global value的身影。
2. 用Python代码编译Python代码
在使用python命令运行脚本时,尽管将Python源代码编译成了字节码,但并没有将编译结果保存成文件,而一切都是在内存中完成的。如果频繁运行Python的某段程序,运行的实际上是内存中的Python字节码。不过在发布时,我们期望像Java一样,可以发布.class文件,其实Python也有类似的文件,这就是.pyc文件。
用Python代码和命令行都可以将Python源代码编译成.pyc文件,只是在默认情况下,Python做得比较隐蔽,会将.pyc文件生成到一个默认的目录,而且很多IDE(如PyCharm)是不会显示这个目录的。这个目录就是__pycache__。
现在做一个实验,首先创建一个demo.py文件,然后输入下面的代码:
现在执行下面的代码将demo.py文件编译生成.pyc文件。
so easy,只需要两行代码(还有一行是import语句),就可以编译demo.py,运行程序后,如果在IDE中,什么都不会发生,别急,切换到demo.py文件所在的目录,会看到多了一个__pycache__目录,打开一看,目录里有一个名为demo.cpython-38.pyc的文件。在读者的机器上文件名可能不同,差异就在最后的数字上,这里的38表示我用的Python版本是3.8,这里不会显示小版本号。如果读者使用的是3.7,那么生成的.pyc文件就是demo.cpython-37.pyc。
现在进入控制台,进入demo.cpython-38.pyc文件所在的目录,执行python demo.cpython-38.pyc命令,同样可以输出结果,与python demo.py执行的结果完全相同。所以在发布Python应用时,可以直接发布pyc文件。
compile函数在编译Python文件时,可以指定第2个参数值,表示要生成的.pyc文件名,这样就可以指定将pyc文件放到特定的目录,代码如下:
执行这段代码,可以在当前目录生成一个名为demo.pyc的文件,执行python demo.pyc命令,同样会得到我们期望的结果。
如果需要编译的Python脚本太多,可以多次调用compile函数,也可以使用compileall模块中的compile_dir函数递归编译指定目录中的所有Python脚本文件。
现在做一个实验,在当前目录创建3层子目录:aa/bb/cc,并在每一层目录创建一个或多个Python脚本文件,可以不写任何代码(空文件即可),如图1所示。
现在执行下面的代码编译aa目录中所有的Python脚本文件。
执行这段代码,首先会递归扫描所有的目录,然后会编译所有发现的Python脚本文件,如图2所示。
查看这几个目录,每一个目录都有一个名为__pycache__目录,里面是对应的pyc文件。
如果不想递归编译所有目录中的Python脚本文件,可以使用compile_dir函数的第2个参数指定递归层次,0表示当前目录(不递归),1表示递归一层目录,以此类推。例如,下面的代码只编译当前目录中所有的Python脚本文件。
3. 在命令行中编译Python脚本
python命令同样可以将.py文件编译成.pyc文件,例如,如果要编译demo.py文件,可以使用下面的命令:
python -m demo.py
这里的-m命令行参数表示编译demo.py,执行这行命令后,会在当前目录的__pycache__目录生成demo.cpython-38.pyc文件,然后可以使用python直接执行这个文件。
如果想递归编译目录中所有的Python文件,可以使用下面的命令:
python -m compileall aa
这行命令可以递归编译aa目录中的所有Python文件。如果还想对编译结果进行优化,可以加-O或-OO,那么这两个优化参数有什么区别呢?
如果不加优化参数,只加-m,那么就不会进行优化,也就是优化层次(Level)为0,当不优化时,Python的内部变量__debug__为True,读者可以在Python Shell中输出这个变量值。如果设置了-O参数,那么优化层次是1,在这一优化层次,会将__debug__变量的值设为False。如果使用-OO参数,优化层次是2,不仅将__debug__变量的值设为False,而且将Python中的docstrings也去除了。docstrings就是Python中的文档注释,可以用来为API自动产生文档。也就是3对单引号或双引号括起来的部分。
其中上一部分讲的compile函数和compile_dir函数也有设置优化level的参数,就拿compile函数来说,该函数的第4个参数用于设置优化层次,默认值是-1,相当于-O参数。还可以设置为0(不优化)、1(与默认值相同)和2(相当于-OO参数)。下面的代码用level = 2的层次优化编译demo.py。
py_compile.compile('demo.py', 'demo.pyc', False, 2)
其实这里的优化,并不是指优化Python Byte Code,而是去掉不同的调试信息和文档。这里的调试信息主要是指为了在Console或日志中输出的一些用于展示程序执行状态的信息。如果这些随着程序发布,会让程序运行效率大打折扣。因为执行在Console或日志中输出信息的代码是很慢的(相对于直接在内存中执行的代码)。
如果使用命令行方式优化编译.py文件,如果使用的是-O参数,生成的目标文件是:demo.cpython-38.opt-1.pyc,如果使用的是-OO参数,生成的目标文件是:demo.cpython-38.opt-2.pyc。
4. 如何对Python代码加密
尽管可以将.py文件编译生成.pyc文件,但.pyc文件和Java的.class文件一样,很容易被反编译。更稳妥的方式是制作一个私有的Python编译和运行环境,说白了,就是修改Python编译器的源代码。听着很高大上,其实并不复杂,只需要修改其中的常量即可。
首先下载Python源代码,然后找到如下两个文件:
<Python源代码根目录>/Lib/opcode.py <Python源代码根目录>/Include/opcode.h
大家可以打开这两个文件看看,opcode.py文件中的代码片段是这样的:
opcode.h文件中的代码片段是这样的:
我们可以看到,在opcode.h文件中定义了一堆宏(相当于常量),而opcode.py文件中同样定义了与opcode.h同名的值,对应的整数值也相等。做过编译器的同学应该能猜出来这是什么东西,其实就是Python Byte Code对应的指令编码。编译出来的.pyc文件都是由这些指令组成的。例如,for指令定义如下:
也就是说,如果Python代码中有for循环,就一定会有这个指令。我们可以做个试验,下面有一段包含1个for循环的Python代码:
输出这段这段代码的Python字节码,如下:
我们可以看到,第4行就是FOR_ITER指令,每一条指令由2个字节组成,第1个字节表示指令本身,第2个字节表示操作数。而在第11行的JUMP_ABSOLUTE指令是跳转指令,FOR_ITER与JUMP_ABSOLUTE配合才能形成循环。JUMP_ABSOLUTE直接跳到了6,也就是FOR_ITER指令所在的位置。
由于FOR_ITER指令对应的数值是93,这是十进制,转换为十六进制是5d,如果考虑后面的操作数12(十六进程是0C,至于为什么操作数是12,这是FOR_ITER指令的特性,读者可以查阅Python字节码的相关文档,这个问题与本文无关,这里先不做阐述),那么完整的指令应该是5d0c。所以编译demo.py,生成对应的.pyc文件,然后打开.pyc文件(用可以查看二进制数据的软件打开),会看到如图1所示的十六进制形式的代码,在第6行可以找到5d0c,这就是for循环的起始指令。
读者可以再加一个for循环,代码如下:
查看pyc文件的代码,会看到如图4的形式。很明显,第6行和第7行都有5d0c指令,这就表明这段代码中包含2条for语句。
Python字节码的反编译器都是根据这些规则实现的,但问题是,如果5d不表示for循环,而表示if语句,那么原有的反编译器岂不是不好使了。
如果在代码中有if语句,那么根据不同的场景,会使用POP_JUMP_IF_FALSE指令或POP_JUMP_IF_TRUE指令,这两条指令在opcode.h的定义如下:
如果有下面的Python代码:
那么会使用POP_JUMP_IF_FALSE指令,这时pyc代码中就会包含72(114的十六进制表示),但如果将FOR_ITER的93和POP_JUMP_IF_FALSE的114调换一下,变成如下形式,那么按Python的标准指令会将for当成if,if当成for,这样反编译出来的代码就乱套了。而反编译器是无法知道你是如何互换指令值得。这就像直接用标准的base64编码是无法加密的,但如果将标准的base64编码随机打乱,用这个打乱的base64编码规则进行编码,是无法用标准的base64编码表解码的。除非拿到了变化后的base64编码表,如果要测试每一种排列,会有64的阶乘这么多种可能,在有限的时间内是根本不可能破解的。而这种修改Python源代码的方式,就相当于打乱标准base64编码表的顺序,增加了破解的难度和时间。
另外,光修改前面介绍的两个文件还不行,还需要修改另外一个文件,路径如下:
<Python源代码根目录>/Python/opcode_targets.h
读者可以打开这个文件,看看为什么要修改这个文件,文件的代码片段如下:
很明显,这段代码用来定义Python字节码的指令,而在opcode.h文件中定义的每一个宏对应的值,就是opcode_targets数组的索引。我们知道,C语言数组索引从0开始,所以opcode_targets数组的第1个元素是一个占位符(&&_unknown_opcode),而POP_TOP指令在opcode.h文件中值正是1,所以正好与opcode_targets数组的第2个元素对应。
我们可以继续查看opcode_targets数组的代码,看到下面的代码形式:找到TARGET_INPLACE_TRUE_DIVIDE,对应的是INPLACE_TRUE_DIVIDE指令,如图5所示。
然后在opcode.h文件中找到INPLACE_TRUE_DIVIDE指令,正好值是29,正好对应opcode_targets中索引为29的元素值。而TARGET_INPLACE_TRUE_DIVIDE下面是一堆&&_unknown_opcode占位符,这也说明INPLACE_TRUE_DIVIDE后面有很多空闲的值,再看看opcode.h文件中的定义,如图6所示。
很显然,INPLACE_TRUE_DIVIDE指令后面的RERAISE指令就直接从48开始了,所以要用多个&&_unknown_opcode作为占位符,否则就无法找到对应的指令了。
所以修改Python源代码要遵循下面的规则:
(1)修改opcode.py文件和opcode.h文件中代码,要统一互换,不能只互换一个;
(2)然后将opcode_targets.h中opcode_targets数组的相对位置也换过来,否则就无法找到对应的指令了;
都改完了,然后就可以编译Python代码了,执行下面的命令即可:
configure
make
make install
最后在发布程序时,需要带上自己编译的Python环境,标准的Python环境已经无法运行我们自己生成的pyc文件了。
当然,包含Python代码的方式有好很多种,例如,对Python代码混淆、将Python代码转换成C代码等等,这些内容我后面会专门写文章讲解。
相关推荐
- 安全教育登录入口平台(安全教育登录入口平台官网)
-
122交通安全教育怎么登录:122交通网的注册方法是首先登录网址http://www.122.cn/,接着打开网页后,点击右上角的“个人登录”;其次进入邮箱注册,然后进入到注册页面,输入相关信息即可完...
- 大鱼吃小鱼经典版(大鱼吃小鱼经典版(经典版)官方版)
-
大鱼吃小鱼小鱼吃虾是于谦跟郭麒麟的《我的棒儿呢?》郭德纲说于思洋郭麒麟作诗的相声,最后郭麒麟做了一首,师傅躺在师母身上大鱼吃小鱼小鱼吃虾虾吃水水落石出师傅压师娘师娘压床床压地地动山摇。...
-
- 哪个软件可以免费pdf转ppt(免费的pdf转ppt软件哪个好)
-
要想将ppt免费转换为pdf的话,我们建议大家可以下一个那个wps,如果你是会员的话,可以注册为会员,这样的话,在wps里面的话,就可以免费将ppt呢转换为pdfpdf之后呢,我们就可以直接使用,不需要去直接不需要去另外保存,为什么格式转...
-
2026-02-04 09:03 off999
- 电信宽带测速官网入口(电信宽带测速官网入口app)
-
这个网站看看http://www.swok.cn/pcindex.jsp1.登录中国电信网上营业厅,宽带光纤,贴心服务,宽带测速2.下载第三方软件,如360等。进行在线测速进行宽带测速时,尽...
- 植物大战僵尸95版手机下载(植物大战僵尸95 版下载)
-
1可以在应用商店或者游戏平台上下载植物大战僵尸95版手机游戏。2下载教程:打开应用商店或者游戏平台,搜索“植物大战僵尸95版”,找到游戏后点击下载按钮,等待下载完成即可安装并开始游戏。3注意:确...
- 免费下载ppt成品的网站(ppt成品免费下载的网站有哪些)
-
1、Chuangkit(chuangkit.com)直达地址:chuangkit.com2、Woodo幻灯片(woodo.cn)直达链接:woodo.cn3、OfficePlus(officeplu...
- 2025世界杯赛程表(2025世界杯在哪个国家)
-
2022年卡塔尔世界杯赛程公布,全部比赛在卡塔尔境内8座球场举行,2022年,决赛阶段球队全部确定。揭幕战于当地时间11月20日19时进行,由东道主卡塔尔对阵厄瓜多尔,决赛于当地时间12月18日...
- 下载搜狐视频电视剧(搜狐电视剧下载安装)
-
搜狐视频APP下载好的视频想要导出到手机相册里方法如下1、打开手机搜狐视频软件,进入搜狐视频后我们点击右上角的“查找”,找到自已喜欢的视频。2、在“浏览器页面搜索”窗口中,输入要下载的视频的名称,然后...
- 永久免费听歌网站(丫丫音乐网)
-
可以到《我爱音乐网》《好听音乐网》《一听音乐网》《YYMP3音乐网》还可以到《九天音乐网》永久免费听歌软件有酷狗音乐和天猫精灵,以前要跳舞经常要下载舞曲,我从QQ上找不到舞曲下载就从酷狗音乐上找,大多...
- 音乐格式转换mp3软件(音乐格式转换器免费版)
-
有两种方法:方法一在手机上操作:1、进入手机中的文件管理。2、在其中选择“音乐”,将显示出手机中的全部音乐。3、点击“全选”,选中所有音乐文件。4、点击屏幕右下方的省略号图标,在弹出菜单中选择“...
- 电子书txt下载(免费的最全的小说阅读器)
-
1.Z-library里面收录了近千万本电子书籍,需求量大。2.苦瓜书盘没有广告,不需要账号注册,使用起来非常简单,直接搜索预览下载即可。3.鸠摩搜书整体风格简洁清晰,书籍资源丰富。4.亚马逊图书书籍...
- 最好免费观看高清电影(播放免费的最好看的电影)
-
在目前的网上选择中,IMDb(互联网电影数据库)被认为是最全的电影网站之一。这个网站提供了各种类型的电影和电视节目的海量信息,包括剧情介绍、演员表、评价、评论等。其还提供了有关电影制作背后的详细信息,...
- 孤单枪手2简体中文版(孤单枪手2简体中文版官方下载)
-
要将《孤胆枪手2》游戏的征兵秘籍切换为中文,您可以按照以下步骤进行操作:首先,打开游戏设置选项,通常可以在游戏主菜单或游戏内部找到。然后,寻找语言选项或界面选项,点击进入。在语言选项中,选择中文作为游...
欢迎 你 发表评论:
- 一周热门
-
-
抖音上好看的小姐姐,Python给你都下载了
-
全网最简单易懂!495页Python漫画教程,高清PDF版免费下载
-
飞牛NAS部署TVGate Docker项目,实现内网一键转发、代理、jx
-
win7系统还原步骤图解(win7还原电脑系统的步骤)
-
Python 3.14 的 UUIDv6/v7/v8 上新,别再用 uuid4 () 啦!
-
python入门到脱坑 输入与输出—str()函数
-
16949认证费用是多少(16949审核员太难考了)
-
linux软件(linux软件图标)
-
Python三目运算基础与进阶_python三目运算符判断三个变量
-
windows7旗舰版多少钱(win7旗舰版要多少钱)
-
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
