Skip to content

有些时候处于代码保密的要求,会需要把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安装文档,里面包含了aptyum形式的安装

单文件编译

以下部分来源于nuitka官方文档

创建一个hello.py

python
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()

当前目录文件为

shell
$ 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.10django4.*版本会遇到这个问题,看网络上其他人的教程中没有出现这个问题,后期可能会有修复吧

    如下错误可以通过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文件

shell
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操作

shell
#!/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文件

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文件

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,也可以使用一些加密参数在打包时候进行代码加密)

参考阅读

Nuitka官方项目

Nuitka官方文档

Last updated:

Released under the MIT License.