python项目加密

Python 语法简单,使用方便,我们可以使用它快速地编写程序和构建应用。在编写好程序之后,我们必然要进行程序的分发。

而这,就涉及到了源码保护的问题。我们不需要程序的使用者能够看到程序的源码。但是,Python 作为一门动态语言和脚本语言,运行通过它编写的程序,并不需要进行静态编译和打包的过程,对其代码进行加密是一件很麻烦、复杂和困难的事情

虽说 Cython 的主要目的是带来性能的提升,但是基于它的原理:将 .py/.pyx 编译为 .c 文件,再将 .c 文件编译为 .so(Unix) 或 .pyd(Windows),其带来的另一个好处就是难以破解。而且文件可直接import

由于项目交付需要,尝试使用Cython对项目进行编译,额外的好处是可以继续使用现有的docker环境、镜像、以及docker-compose进行部署

一、环境

需要使用docker上使用的python编译,python版本和gcc库都会影响文件的使用

gcc,这里使用的是gcc-7

Cython==0.29.35, 这个版本不是必须的

1、gcc安装

这里介绍ubuntu18.04版本下gcc的安装过程

  • 编辑apt源

    vim /etc/apt/sources.list
    

    用下面内容替换

    # 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
    # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
    # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
    deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
    # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
    
    # deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
    # # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
    
    deb http://security.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse
    # deb-src http://security.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse
    
  • 更新apt

    apt-get update
    
  • 由于这里用的python3.7,还需安装python3.7-dev,否则gcc编译时会找不到"Python.h"等头文件默认的 Ubuntu 软件源包含了一个软件包组,名称为 “build-essential”,它包含了 GNU 编辑器集合,GNU 调试器,和其他编译软件所必需的开发库和工具。

    apt-get install gcc-7 build-essential python3.7-dev -y
    

2、Cython安装

这个简单,直接pip安装即可

pip3.7 install Cython==0.29.35

二、编译

由于Cython会出现目录结构错误问题,需要编写代码控制编译过程,并清理编译中间产物。需注意留下启动入口文件

编写setup.py,内容如下。并将文件放到项目根目录

import os
import platform
import shutil
from distutils.core import setup

from Cython.Build import cythonize

package_name = "package"
ignore_dirs = ['__pycache__', 'build', 'dist', package_name, 'static', 'bk', 'img_data', 'model']
ignore_file_in_root = ['setup.py', 'setup_main.py', 'requirements.txt', 'proxy.py', 'success.flag']
# 项目子目录下不用(能)转译的'py文件(夹)名
ignore_names = {}
# 需要直接拷贝到编译文件夹的文件
need_move = ['proxy.py']
# 需要编译的文件夹绝对路径
base_dir = os.path.dirname(os.path.abspath(__file__))
# 将以上不需要转译的文件(夹)加上绝对路径
ignore_files = {os.path.join(base_dir, x) for x in ignore_dirs}
ignore_files.update({os.path.join(base_dir, x) for x in ignore_file_in_root})
ignore_files.update({os.path.join(base_dir, x) for x in need_move})
need_move_files = [os.path.join(base_dir, x) for x in need_move]
files = []

build_error_files = []
if os.path.exists(os.path.join(base_dir, package_name)):
    shutil.rmtree(os.path.join(base_dir, package_name))

if os.path.exists(os.path.join(base_dir, 'build')):
    shutil.rmtree(os.path.join(base_dir, 'build'))

package_path = os.path.join(base_dir, package_name)
# 若没有打包文件夹,则生成一个
if not os.path.exists(package_path):
    os.mkdir(package_path)

sys = platform.system()
build_dir = ""


# 由于生成文件的路径有错误,只能看目标文件夹是否出现了新的文件,而且没办法使用多线程处理
def get_new_file(original_file_name):
    global build_dir
    if not build_dir:
        for file_dir in os.listdir(os.path.join(base_dir, "build")):
            if sys == "Windows":
                if file_dir.startswith("lib.win"):
                    build_dir = os.path.join(base_dir, "build", file_dir)
                    break
            elif sys == "Linux":
                if file_dir.startswith("lib.linux"):
                    build_dir = os.path.join(base_dir, "build", file_dir)
                    break
        print("build_dir: ", build_dir)
    if not build_dir:
        raise Exception("build_dir is None")
    for root, dirs, files in os.walk(build_dir):
        for file in files:
            file_path = os.path.join(root, file)
            if file.startswith(original_file_name):
                return file_path
    raise Exception("No new file generated")


# 获取全部待处理文件
def get_files(dir_path):
    for file_dir in os.listdir(dir_path):
        file_dir_path = os.path.join(dir_path, file_dir)
        if file_dir.endswith(".c"):
            os.remove(file_dir_path)
            continue
        elif file_dir in ignore_names:
            continue
        elif file_dir.endswith(".pyc"):
            continue
        if dir_path == base_dir and file_dir_path in ignore_files:
            continue
        if os.path.isdir(file_dir_path):
            get_files(file_dir_path)
        else:
            files.append(file_dir_path)


# 如果是py文件,需要在文件头加上cython的语句,然后编译,编译完成后再去掉cython的语句
# 如果是其他文件,直接拷贝到目标文件夹
def process_file(file_path):
    source = file_path
    build_error = False
    if file_path.endswith(".py"):
        _, file_name = os.path.split(file_path)
        with open(file_path, 'r', encoding='utf8') as f:
            content = f.read()
            if not content.startswith('# cython: language_level=3'):
                content = '# cython: language_level=3\n' + content
                with open(file_path, 'w', encoding='utf8') as f1:
                    f1.write(content)
        try:
            setup(name="asr_attack", ext_modules=cythonize([file_path]), script_args=["build_ext"])
        except Exception as e:
            print("cythonize error: ", e)
            build_error_files.append(file_path)
            build_error = True
        with open(file_path, 'r', encoding='utf8') as f:
            content = f.read()
            if content.startswith('# cython: language_level=3'):
                content = content.split("\n", 1)[1]
                with open(file_path, 'w', encoding='utf8') as f1:
                    f1.write(content)
        if not build_error:
            source = get_new_file(file_name.split(".")[0])
    print("source: ", source)
    dir_name, file_name = os.path.split(file_path.replace(base_dir + os.path.sep, ""))
    if dir_name:
        dir_name = os.path.join(package_path, dir_name)
        if not os.path.exists(dir_name):
            os.makedirs(dir_name, exist_ok=True)
    else:
        dir_name = package_path
    suffix = source.split(".")[-1]
    file_name = f'{file_name.split(".")[0]}.{suffix}'
    shutil.copy(source, os.path.join(dir_name, file_name))
    if not build_error and (source.endswith(".so") or source.endswith(".pyd")):
        os.remove(source)


if __name__ == '__main__':
    get_files(base_dir)
    print("*" * 50, "get_files finish", "*" * 50)
    print("*" * 50, "need process files: ", len(files), "*" * 50)
    i = 1
    for file in files:
        print("*" * 50, f"processing: {i} / {len(files)}", "*" * 50)
        process_file(file)
        i += 1
    print("*" * 50, "process_file finish" + "*" * 50)
    for file in need_move_files:
        shutil.copy(file, os.path.join(package_path, os.path.basename(file)))
    print("*" * 50, "copy_files finish" + "*" * 50)
    for root, dirs, files in os.walk(base_dir):
        for file in files:
            if file.endswith(".c"):
                os.remove(os.path.join(root, file))
    if os.path.exists(os.path.join(base_dir, 'build')):
        shutil.rmtree(os.path.join(base_dir, 'build'))
    print("*" * 50, "clean_files finish" + "*" * 50)
    # 将编译失败的文件打印出来
    if build_error_files:
        print("*" * 50, "build_error_files", "*" * 50)
        for file in build_error_files:
            print(file)

执行

python3.7 setup.py

结束后,package目录下就是项目文件了

三、其他问题

py文件不能出现直接引用同级目录文件的情况

如果某个文件执行错误,可以将有py文件替换掉so文件(个别错误可以这么办)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值