有些时候处于代码保密的要求,会需要把python
代码进行加密或者编译加密等来实现保密
原理
Python
是一种面向对象的解释型计算机程序设计语言,解释特性是将py
编译为独有的二进制编码*.pyc
文件,对pyc
中的指令进行解释执行,但是pyc
的反编译非常简单,可直接反编译为源码由于基于虚拟机的编程语言(解释型语言)比如
java
或者python
很容易被人反编译,因此越来越多的应用将其中的核心代码以C/C++
为编程语言,并且以*.so
文件的形式提供在
windows
环境下面经常会看到*.dll
文件,在Linux
环境下经常会看到*.so
文件,这两种都是动态库,*.so
文件可以称为动态链接库或者共享库,是ELF
文件格式,也是一种二进制文件,一般是C
或者C++
编译出来的
虽然目前有一些反编译手段可以去反编译so
文件,但是好像效果都不怎么样,反编译出来的都是一堆比较混乱的C
语言代码,我们是用python
编写的程序,所以把Python
代码打包成so
文件是可以达到加密的要求的
技术依赖
开源项目Nuitka
,采用Apache-2.0 license
协议
安装nuitka
pip
方式
python -m pip install -U nuitka
python -m pip install -U nuitka
在Ubuntu22
下面使用python3.10
是可以成功安装的,其他操作系统或者python
版本如果安装失败
请详细阅读官方nuitka
安装文档,里面包含了apt
,yum
形式的安装
单文件编译
以下部分来源于nuitka
官方文档
创建一个hello.py
def talk(message):
return "Talk " + message
def main():
print(talk("Hello World"))
if __name__ == "__main__":
main()
def talk(message):
return "Talk " + message
def main():
print(talk("Hello World"))
if __name__ == "__main__":
main()
当前目录文件为
$ ll
总用量 4.0K
-rw-rw-r-- 1 gong gong 133 九月 22 12:52 hello.py
$ ll
总用量 4.0K
-rw-rw-r-- 1 gong gong 133 九月 22 12:52 hello.py
开始编译
由于之前是采用pip
方式安装的nuitka
,所以使用的时候需要带上python -m
的前缀
如果是使用apt
或者yum
等安装的,直接运行nuitka
或者nuitka3
即可
运行如下命令可以在当前目录下面创建一个hello.bin
的文件
$ python -m nuitka hello.py --remove-output
....
$ python -m nuitka hello.py --remove-output
....
参数解释
--remove-output
参数表示在生成二进制编译文件之后移除编译构建目录,该目录是编译过程中会使用到,编译结束之后即可删除
运行二进制文件
$ ./hello.bin
Talk Hello World
$ ./hello.bin
Talk Hello World
项目级别编译
下面采用django
项目进行演示,出于方便采用sqlite
数据库,生产环境请使用其他数据库
创建示范项目
$ pip install django -i https://pypi.doubanio.com/simple/
$ django-admin startproject hellonuitka
$ cd hellonuitka
$ python manage.py migrate
$ python manage.py runserver
$ pip install django -i https://pypi.doubanio.com/simple/
$ django-admin startproject hellonuitka
$ cd hellonuitka
$ python manage.py migrate
$ python manage.py runserver
查看初始路径树
(tree
可以通过sudo apt/yum install tree
安装)
$ tree
.
├── db.sqlite3
├── hellonuitka
│ ├── asgi.py
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ ├── settings.cpython-310.pyc
│ │ ├── urls.cpython-310.pyc
│ │ └── wsgi.cpython-310.pyc
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
2 directories, 11 files
$ tree
.
├── db.sqlite3
├── hellonuitka
│ ├── asgi.py
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ ├── settings.cpython-310.pyc
│ │ ├── urls.cpython-310.pyc
│ │ └── wsgi.cpython-310.pyc
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
2 directories, 11 files
开始编译
在项目根目录下(manage.py
同级目录)开始进行编译,编译的过程当中需要指定编译的文件夹名称
$ python -m nuitka --module hellonuitka --include-package=hellonuitka --remove-output
$ python -m nuitka --module hellonuitka --include-package=hellonuitka --remove-output
命令执行完成之后查看目录树,发现多了一个hellonuitka.cpython-310-x86_64-linux-gnu.so
文件以及hellonuitka.pyi
$ tree
.
├── db.sqlite3
├── hellonuitka
│ ├── asgi.py
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ ├── settings.cpython-310.pyc
│ │ ├── urls.cpython-310.pyc
│ │ └── wsgi.cpython-310.pyc
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── hellonuitka.cpython-310-x86_64-linux-gnu.so
├── hellonuitka.pyi
└── manage.py
$ tree
.
├── db.sqlite3
├── hellonuitka
│ ├── asgi.py
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ ├── settings.cpython-310.pyc
│ │ ├── urls.cpython-310.pyc
│ │ └── wsgi.cpython-310.pyc
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── hellonuitka.cpython-310-x86_64-linux-gnu.so
├── hellonuitka.pyi
└── manage.py
生成文件解释
hellonuitka.pyi
文件内容如下,这个文件包含了包里面的导入信息(官方文档的描述是说是用于检测隐式导入的)
在standalone
模式创建的库会需要使用到这个文件,目前使用的模式不需要这个文件,可以直接删除,或者在执行命令的时候添加参数--no-pyi-file
不生成这个文件
....
....
import django.core.asgi
import pathlib
import django.contrib
import django.urls
import django.core.wsgi
....
....
....
....
import django.core.asgi
import pathlib
import django.contrib
import django.urls
import django.core.wsgi
....
....
hellonuitka.cpython-310-x86_64-linux-gnu.so
由打包之前的文件夹hellonuitka
组成,后面拼接的是cpython-{python版本号}-{cpu架构}-{操作系统信息}
,这个是一个二进制文件,可以用于等效替换hellonuitka
验证编译结果
移动旧的源码文件夹
$ mv hellonuitka /tmp/hellonuitka
$ mv hellonuitka /tmp/hellonuitka
删除不必要的*.pyi
文件
$ rm hellonuitka.pyi
$ rm hellonuitka.pyi
删除生成的sqlite
数据库,方便测试后面的migrate
命令是否正常
$ rm db.sqlite3
$ rm db.sqlite3
最后在编译项目根目录下的python
源码
开始进行验证,查看项目路径树
$ ll
总用量 224K
-rw-rw-r-- 1 gong gong 218K 九月 22 15:09 hellonuitka.cpython-310-x86_64-linux-gnu.so
-rwxrwxr-x 1 gong gong 667 九月 22 12:00 manage.py
$ ll
总用量 224K
-rw-rw-r-- 1 gong gong 218K 九月 22 15:09 hellonuitka.cpython-310-x86_64-linux-gnu.so
-rwxrwxr-x 1 gong gong 667 九月 22 12:00 manage.py
开始执行命令,发现都正常运行
$ python manage.py migrate
$ python manage.py startapp app1
$ python manage.py runserver
.....
$ python manage.py migrate
$ python manage.py startapp app1
$ python manage.py runserver
.....
可能遇到的错误
manage.py
转成manage.bin
之后的报错
$ python -m nuitka manage.py --remove-output
$ mv manage.py /tmp/manage.py
$ python -m nuitka manage.py --remove-output
$ mv manage.py /tmp/manage.py
查看文件
$ ll
总用量 6.2M
-rw-r--r-- 1 gong gong 128K 九月 22 16:24 db.sqlite3
-rw-rw-r-- 1 gong gong 218K 九月 22 17:16 hellonuitka.cpython-310-x86_64-linux-gnu.so
-rwxrwxr-x 1 gong gong 5.8M 九月 22 17:20 manage.bin
$ ll
总用量 6.2M
-rw-r--r-- 1 gong gong 128K 九月 22 16:24 db.sqlite3
-rw-rw-r-- 1 gong gong 218K 九月 22 17:16 hellonuitka.cpython-310-x86_64-linux-gnu.so
-rwxrwxr-x 1 gong gong 5.8M 九月 22 17:20 manage.bin
运行下列命令都是可以正常的
$ ./manage.bin migrate
$ ./manage.bin startapp app2
$ ./manage.bin migrate
$ ./manage.bin startapp app2
但是./manage.bin runserver
就不行了,错误如下
$ ./manage.bin runserver
SyntaxError: Non-UTF-8 code starting with '\x80' in file /home/gong/work/hellonuitka/./manage.bin on line 2, but no encoding declared; see https://python.org/dev/peps/pep-0263/ for details
$ ./manage.bin runserver
SyntaxError: Non-UTF-8 code starting with '\x80' in file /home/gong/work/hellonuitka/./manage.bin on line 2, but no encoding declared; see https://python.org/dev/peps/pep-0263/ for details
会报错不支持 UTF-8
编码
主要是因为manage.py
启动服务是通过命令掉包反射机制完成包导入进行服务启动的,导致不支持runserver
命令,其他命令还是可以正常运行的
如果对于manage.py
没有什么重要信息的话就可以不用编译这个文件了,以免不必要的麻烦
运行
gunicorn hellonuitka.wsgi
错误在
python3.10
和django4.*
版本会遇到这个问题,看网络上其他人的教程中没有出现这个问题,后期可能会有修复吧如下错误可以通过
mv hellonuitka.cpython-310-x86_64-linux-gnu.so hellonuitka.so
ModuleNotFoundError: No module named 'hellonuitka'
ModuleNotFoundError: No module named 'hellonuitka'
但是还是会遇到错误,去搜索了一圈没找到有什么解决方案,如果也遇到了相同的问题,并且找不到解决方案的话,建议采用
uwsgi
,该方式是可以正常启动的(
Tips
: 如果是采用docker
环境部署服务,如采用python:3.10.7-slim-bullseye
镜像进行编译之后(其他版本的python docker
镜像可能也是一样的),gunicorn
服务是可以正常启动的,也不会遇到上面的No module named 'hellonuitka'
错误,所以大千世界神秘莫测)undefined symbol: PyDescr_IsData
undefined symbol: PyDescr_IsData
非docker
环境编译加密
在项目根目录下面执行此命令,之后再删除python
源代码文件即可,只保留so
文件
for dir in $(ls -d */)
do
python -m nuitka --module ${dir%?} --include-package=${dir%?} --remove-output --no-pyi-file
done
for dir in $(ls -d */)
do
python -m nuitka --module ${dir%?} --include-package=${dir%?} --remove-output --no-pyi-file
done
docker
容器镜像编译加密
创建一个requirements.txt
,写入如下依赖
gunicorn
django
nuitka
gevent
gunicorn
django
nuitka
gevent
新建一个compile.sh
,由于该脚本是在容器构建过程中使用,所以可以执行rm
操作
#!/bin/bash
for dir in $(ls -d */)
do
python -m nuitka --module ${dir%?} --include-package=${dir%?} --remove-output --no-pyi-file
rm -rf ${dir}
done
#!/bin/bash
for dir in $(ls -d */)
do
python -m nuitka --module ${dir%?} --include-package=${dir%?} --remove-output --no-pyi-file
rm -rf ${dir}
done
创建一个Dockerfile
文件
FROM python:3.10.7-slim-bullseye
WORKDIR /work
# 由于需要编译python,所以需要安装gcc
RUN sed -i "s#http://deb.debian.org#https://mirrors.ustc.edu.cn#g" /etc/apt/sources.list \
&& apt update \
&& apt install -y gcc \
&& rm -rf /var/lib/apt/lists/ \
&& rm -rf /var/cache/apt/archives
ADD requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.doubanio.com/simple/
COPY . .
RUN bash compile.sh && rm compile.sh requirements.txt
FROM python:3.10.7-slim-bullseye
WORKDIR /work
# 由于需要编译python,所以需要安装gcc
RUN sed -i "s#http://deb.debian.org#https://mirrors.ustc.edu.cn#g" /etc/apt/sources.list \
&& apt update \
&& apt install -y gcc \
&& rm -rf /var/lib/apt/lists/ \
&& rm -rf /var/cache/apt/archives
ADD requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.doubanio.com/simple/
COPY . .
RUN bash compile.sh && rm compile.sh requirements.txt
采用docker compose
方式部署
项目根目录下面新建.dockerignore
排除不必要的文件以及一些重要信息文件
Dockerfile
compose.yaml
.git/
.idea/
.dockerignore
Dockerfile
compose.yaml
.git/
.idea/
.dockerignore
新建compose.yaml
文件
services:
hellonuitka:
build: .
container_name: hello
image: hello
ports:
- 8000:8000
restart: always
command:
- /bin/sh
- -c
- |
python manage.py migrate &&
gunicorn hellonuitka.wsgi --bind=0.0.0.0:8000 --workers=4 --worker-connections=1000 --worker-class=gevent
services:
hellonuitka:
build: .
container_name: hello
image: hello
ports:
- 8000:8000
restart: always
command:
- /bin/sh
- -c
- |
python manage.py migrate &&
gunicorn hellonuitka.wsgi --bind=0.0.0.0:8000 --workers=4 --worker-connections=1000 --worker-class=gevent
此时项目的路径树如下
├── app2
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── compile.sh
├── compose.yaml
├── Dockerfile
├── hellonuitka
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── requirements.txt
├── app2
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── compile.sh
├── compose.yaml
├── Dockerfile
├── hellonuitka
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── requirements.txt
开始进行构建,由于是第一次构建镜像所以需要携带--build
参数,命令执行结束之后可以去访问http://127.0.0.1:8000
进行验证服务是否启动成功
$ docker compose up --build
# 可以看到输出中最后几行如下
.....
hello | [2022-09-23 01:35:43 +0000] [8] [INFO] Starting gunicorn 20.1.0
hello | [2022-09-23 01:35:43 +0000] [8] [INFO] Listening at: http://0.0.0.0:8000 (8)
hello | [2022-09-23 01:35:43 +0000] [8] [INFO] Using worker: gevent
hello | [2022-09-23 01:35:43 +0000] [9] [INFO] Booting worker with pid: 9
hello | [2022-09-23 01:35:43 +0000] [10] [INFO] Booting worker with pid: 10
hello | [2022-09-23 01:35:43 +0000] [11] [INFO] Booting worker with pid: 11
hello | [2022-09-23 01:35:43 +0000] [12] [INFO] Booting worker with pid: 12
$ docker compose up --build
# 可以看到输出中最后几行如下
.....
hello | [2022-09-23 01:35:43 +0000] [8] [INFO] Starting gunicorn 20.1.0
hello | [2022-09-23 01:35:43 +0000] [8] [INFO] Listening at: http://0.0.0.0:8000 (8)
hello | [2022-09-23 01:35:43 +0000] [8] [INFO] Using worker: gevent
hello | [2022-09-23 01:35:43 +0000] [9] [INFO] Booting worker with pid: 9
hello | [2022-09-23 01:35:43 +0000] [10] [INFO] Booting worker with pid: 10
hello | [2022-09-23 01:35:43 +0000] [11] [INFO] Booting worker with pid: 11
hello | [2022-09-23 01:35:43 +0000] [12] [INFO] Booting worker with pid: 12
现在进入容器进行编译验证
$ docker exec -it hello /bin/bash
root@f18baf3ec559:/work# ls -al
total 1632
drwxr-xr-x 1 root root 4096 Sep 23 01:39 .
drwxr-xr-x 1 root root 4096 Sep 23 01:39 ..
-rw-r--r-- 1 root root 764584 Sep 23 01:39 app2.cpython-310-x86_64-linux-gnu.so
-rw-r--r-- 1 root root 131072 Sep 22 08:24 db.sqlite3
-rw-r--r-- 1 root root 756264 Sep 23 01:39 hellonuitka.cpython-310-x86_64-linux-gnu.so
-rwxrwxr-x 1 root root 667 Sep 22 09:34 manage.py
$ docker exec -it hello /bin/bash
root@f18baf3ec559:/work# ls -al
total 1632
drwxr-xr-x 1 root root 4096 Sep 23 01:39 .
drwxr-xr-x 1 root root 4096 Sep 23 01:39 ..
-rw-r--r-- 1 root root 764584 Sep 23 01:39 app2.cpython-310-x86_64-linux-gnu.so
-rw-r--r-- 1 root root 131072 Sep 22 08:24 db.sqlite3
-rw-r--r-- 1 root root 756264 Sep 23 01:39 hellonuitka.cpython-310-x86_64-linux-gnu.so
-rwxrwxr-x 1 root root 667 Sep 22 09:34 manage.py
拓展了解
Nuitka
是将python
编译成C
代码 ,再编译成可执行文件,不存在反向解析的问题,非常安全,由于可执行文件由C
编译而来,运行速度也会获得提升,但是在使用nuitka
过程当中还是会有一些问题的,生产环境使用的话尽量做到全量测试,如果遇到一些复杂的编译之后很难解决的问题,也可以考虑一下pyinstaller
,这个开源项目编译操作更简单,相对来说坑也更少一些,但是编译之后的运行速度上不如nuitka
编译之后的软件,并且编译之后的软件的反编译难度比nuitka
更低一些(编译成pyc
,也可以使用一些加密参数在打包时候进行代码加密)