加密编译Python源代码与License控制程序运行(测试全过程)

更多内容请点击 我的博客 查看,欢迎来访。

Python加密方法

不想被别人看到源代码,且不能影响程序的正常运行
在这里插入图片描述

编译成pyc

# 命令运行
python -m py_compile /path/test.py 
# Python代码
import py_compile 
py_compile.compile(r'/path/test.py') 

编译多个文件

import compileall
dirpath = '/path/'
compileall.compile_dir(dirpath)

编译成pyo

pyo仅为pyc的一种优化格式,并不是说加密程度会更高

python -O -m py_compile /path/test.py

编译成pyc或者pyo文件后命名与原来的文件一致,将其放在原来的目录下,虽然其他Python文件调用pyd时显示不能检测到该模块,但实际上可以运行。

由于pyc的编译可能与Python版本有关,如果移动到其他运行,最好Python版本保持一致。

示例:Django之py->pyc

编译文件放置在需要编译的文件夹下面,递归编译所有的py代码

知识点:py编译为pyc,shutil删除文件,移动文件,相对路径,代码当前路径。

#! /usr/bin/env python
# -*- coding: utf-8 -*-

"""
@Version :   Ver1.0
@Author  :   LR
@License :   (C) Copyright 2013-2017, MyStudy
@Contact :   xyliurui@look
@Software:   PyCharm
@File    :   compile_all.py
@Time    :   2018/1/11 15:35
@Desc    :   脚本放置在目录下,操作该目录下的文件
"""

import compileall, py_compile
import re
import os
import shutil

# path = r'G:\LR@ProjectsSync\PycharmProjects\DjangoBookMarks_test_pyc'
path = os.path.dirname(os.path.abspath(__file__))  # 指定项目的路径
# print(path)
print(os.path.abspath(__file__))
if not os.path.exists(os.path.join(path, 'PyBackups')):
    print('新建py备份文件夹~~~')
    os.makedirs(os.path.join(path, 'PyBackups'))
PyBackups = os.path.join(path, 'PyBackups')

# compileall.compile_dir(path, force=True)


def compile_all(path):
    print('尝试编译py文件~~~')
    for root, dirs, files in os.walk(path):
        # print(root)
        # print(True in (map(lambda exclude_path: exclude_path in root, ['migrations'])))
        if True in (map(lambda exclude_path: exclude_path in root, ['migrations', 'static', 'templates', 'PyBackups'])):
            print('········>跳过文件夹:', root)
            continue
        for filename in files:
            if filename.endswith('.py'):
                print('正在编译:', filename)
                src_file = os.path.join(root, filename)
                dst_file = os.path.join(root, filename + 'c')
                if os.path.exists(dst_file):
                    os.remove(dst_file)
                if src_file == os.path.abspath(__file__):
                    # 如果等于自身就跳过
                    continue
                if filename == 'wsgi.py':
                    print('不编译wsgi.py文件!')
                    continue
                py_compile.compile(src_file, cfile=dst_file)

                # 得到文件相对于编译文件的相对路径
                relative_path = os.path.relpath(src_file)
                # print('相对路径:', relative_path)
                # 截取路径中的文件名
                # print('文件名:', os.path.basename(relative_path))
                # print('文件路径:', os.path.dirname(relative_path))
                rel_dir_name = os.path.dirname(relative_path)

                # 如果备份目录下存在这个文件,则将其删除
                if os.path.exists(os.path.join(PyBackups, relative_path)):
                    os.remove(os.path.join(PyBackups, relative_path))

                if rel_dir_name == '':
                    print('········>文件的相对路径为空')
                    shutil.move(src_file, PyBackups)
                else:
                    print('········>文件的相对路径为:', rel_dir_name)
                    py_bak_path = os.path.join(PyBackups, rel_dir_name)
                    if not os.path.exists(py_bak_path):
                        os.makedirs(py_bak_path)
                    shutil.move(src_file, py_bak_path)


def recovery_name(path, PyBackups):
    print('尝试恢复备份的文件~~~')
    os.chdir(PyBackups)
    for root, dirs, files in os.walk(PyBackups):
        for filename in files:
            if filename.endswith('.py'):
                print('正在恢复:', filename)
                src_file_bak = os.path.join(root, filename)
                # print(src_file_bak)
                # 得到文件相对于编译文件的相对路径
                relative_path = os.path.relpath(src_file_bak)
                # print('相对路径:', relative_path)
                print('·                       > ', os.path.join(path, relative_path))
                shutil.move(src_file_bak, os.path.join(path, relative_path))
    os.chdir(path)


def del_bak_path(PyBackups):
    passwd = input("输入删除密码,默认'del':")
    if passwd == 'del':
        shutil.rmtree(PyBackups)


if __name__ == '__main__':
    while True:
        print('\n\nc:编译所有的py文件为pyc\nr:编译过程生成的.py.bak文件恢复为.py文件\nd:清空备份目录,且删除该文件夹\nq:退出选择')
        word = input('请输入选择:')
        if word == 'c':
            compile_all(path)
        elif word == 'r':
            recovery_name(path, PyBackups)
        elif word == 'd':
            del_bak_path(PyBackups)
        else:
            break

pyinstaller打包exe

最好针对单一的文件,需要运行在没有Python环境的Windows系统上

Windows打包Python程序,使用pip安装pyinstaller

UpdateScriptFile.py放在一起,可以在同一目录下放置一个ico图标,运行即可

import os

os.system("pyinstaller -F UpdateScriptFile.py -i update.ico")

编译成pyd或so文件

需要安装pip install pycrypto Cython

使用cpython将Python代码编译成C/C++,然后再编译成Python扩展模块,windows上为pyd文件(pyd文件实际就是dll文件),Linux上为so文件

pyd文件在被其它python文件调用时依然不能被识别,但能够运行

# 命令方式
# (在项目目录下打开命令行或者shell,一次只能编译一个文件,
# 编译之后会现出现三个文件:test.c、test.html、test-win_amd64.pyd(Linux为test.xxx.so),
# 此时将.c、.html和原.py文件删除,将.pyd文件或.so命名更改为test就可以)
cythonize -a -i test.py

Python代码实现Ver0.1

from distutils.core import setup
from Cython.Build import cythonize
import os
import re
import shutil
import sys

# 针对多文件情况设置,单文件就只写一个就行, 文件之间用逗号隔开
module_list = ['t_test.py', 'app/t_core.py', 'app/t_main.py']
# module_list = ['admin.py', 'models.py', 'urls.py', 'views.py']

current_dir = os.getcwd()  # 当前目录,绝对路径
print("当前工作目录:", current_dir)

# 该程序所在目录名称,最后一级目录名;使用setup()会自动在程序目录下生成和所在目录相同名称的文件夹,编译后的文件pyd都放在该文件夹中的
current_dir_name = os.path.basename(current_dir)
print('工作目录名称:', current_dir_name)

# 创建备份源代码文件夹,用于存放编译后相同结构的py文件
backup_dir = os.path.join(current_dir, 'PyBackups')
if not os.path.exists(backup_dir):
    print('新建py备份文件夹:', backup_dir)
    os.makedirs(backup_dir)

file_path_list = [os.path.normpath(os.path.join(current_dir, file)) for file in module_list]  # normpath转为系统的路径格式


# print('处理的文件绝对路径:', file_path_list)


def rreplace(s, old, new, *max):
    """
    从右往左替换
    :param s: 字符串
    :param old: 被替换的字符串
    :param new: 新的字符换
    :param max: 替换次数
    :return:
    """
    count = len(s)
    if max and str(max[0]).isdigit():
        count = max[0]
    return new.join(s.rsplit(old, count))


def backup_py_file(file_path, backup_dir):
    """
    备份指定的文件(绝对路径)到新的文件夹
    :param file_path: 备份文件绝对路径
    :param backup_dir: 备份到的文件夹
    :return:
    """
    # 得到文件相对于编译文件的相对路径,例如 app\t_core.py 对应的相对路径如下:
    file_rel_path = os.path.relpath(file_path)
    # print('相对路径:', file_rel_path)  # app\t_core.py
    # 截取相对路径中的文件名
    # print('文件名:', os.path.basename(file_rel_path))  # t_core.py
    # print('文件路径:', os.path.dirname(file_rel_path))  # app

    # 文件相对路径中的目录部分
    rel_dir_name = os.path.dirname(file_rel_path)
    # 如果备份目录下存在这个文件相对目录,则先进行创建
    file_backup_dir = os.path.join(backup_dir, rel_dir_name)
    if not os.path.exists(file_backup_dir):
        os.makedirs(file_backup_dir)

    # 文件备份的绝对路径
    file_backup_path = os.path.join(backup_dir, file_rel_path)
    print('【备份】py文件移动:{}  ==> {}'.format(file_rel_path, file_backup_path))
    if os.path.exists(file_backup_path):
        os.remove(file_backup_path)
    os.rename(file_path, file_backup_path)


def encrypt_py():
    """
    1、编译指定文件
    2、删除build临时目录
    3、删除.c文件
    4、备份原.py文件
    5、重命名.pyd或.so文件,并移动回原目录
    6、删除编译目录
    :return:
    """
    # 编译
    try:
        # 批量编译
        setup(
            name="StarMeow app",
            ext_modules=cythonize(module_list, language_level=2),
        )
        """
        1、将编译后的pyd、so文件的命名更改成与原py文件一致
        2、删除编译后得到的c文件
        """
    except ValueError as e:
        print(e)
    # 编译结束

    # 删除build目录
    build_path = os.path.join(current_dir, 'build')
    print('\n×      删除build临时目录:{}\n'.format(build_path))
    if os.path.exists(build_path):
        shutil.rmtree(build_path)

    for root, dirs, files in os.walk(current_dir):
        # print(root, dirs, files)
        for file in files:
            # 处理文件
            file_path = os.path.join(root, file)
            # print(file_path)
            if file_path in file_path_list:
                # 删除.c文件:t_core.c
                c_file_path = file_path.replace('.py', '.c')
                print('×      删除.c文件:', c_file_path)
                if os.path.exists(c_file_path):
                    os.remove(c_file_path)

                # 备份原py文件
                backup_py_file(file_path, backup_dir)

            # 重命名.pyd或.so文件:t_core.cp37-win_amd64.pyd --> t_core.pyd
            # 正则匹配名称是file.env.pyd格式的
            if re.match(r"\S+\.\S+\.(pyd|so)", file):
                # print(root, dirs, files)
                file_path_name, file_extension = os.path.splitext(file_path)  # file_path:t_core.cp37-win_amd64.pyd  ==>  t_core.cp37-win_amd64 ,  .pyd
                new_file_path = file_path_name.split('.')[0] + file_extension  # t_core.pyd

                # 文件本应该存放的目录,也就是去掉编译新生成的目录
                if "/" in str(file_path):
                    # Linux系统
                    new_file_path = rreplace(new_file_path, current_dir_name + '/', '', 1)
                else:
                    # Windows系统
                    new_file_path = rreplace(new_file_path, current_dir_name + '\\', '', 1)

                if os.path.exists(new_file_path):
                    # 删除已存在的
                    os.remove(new_file_path)
                print('【整合】.pyd文件移动到目标位置:{}  ==> {}'.format(file_path, new_file_path))
                os.rename(file_path, new_file_path)

    # 删除编译生成的目录
    del_path = os.path.join(current_dir, current_dir_name)
    print('\n×      删除编译目标目录:{}\n'.format(del_path))
    if os.path.exists(del_path):
        shutil.rmtree(del_path)


def recovery_py(backup_dir, current_dir):
    os.chdir(backup_dir)
    print('切换目录:', backup_dir)
    for root, dirs, files in os.walk(backup_dir):
        for file in files:
            if file.endswith('.py'):
                src_file_bak = os.path.join(root, file)
                # print(src_file_bak)
                # 得到文件相对于编译文件的相对路径
                relative_path = os.path.relpath(src_file_bak)
                # print('相对路径:', relative_path)
                rec_file_path = os.path.join(current_dir, relative_path)
                print('【恢复】.py文件移动到原位置:{}  ==>  {}'.format(file, rec_file_path))
                shutil.move(src_file_bak, rec_file_path)

                # py恢复过程中,删除pyd文件
                del_pyd_path = rec_file_path.replace('.py', '.pyd')
                if os.path.exists(del_pyd_path):
                    print('×      删除.pyd文件:', del_pyd_path)
                    os.remove(del_pyd_path)
                del_so_path = rec_file_path.replace('.py', '.so')
                if os.path.exists(del_so_path):
                    print('×      删除.pyd文件:', del_so_path)
                    os.remove(del_so_path)

    os.chdir(current_dir)
    print('返回目录:', current_dir)


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('缺少参数,使用方法:程序放在项目下,指定module_list需要加密的相对路径文件列表\n编译文件:python encrypt.py build_ext --inplace\n恢复文件:python encrypt.py recovery')
    else:
        if sys.argv[1] == 'build_ext':
            encrypt_py()
        elif sys.argv[1] == 'recovery':
            recovery_py(backup_dir, current_dir)
        else:
            print('参数错误')

setup()参数script_args测试

from distutils.core import setup
from Cython.Build import cythonize
import os

setup(
    name="StarMeow app",
    version='0.2',
    description='加密Python源码',
    author='StarMeow',
    author_email='[email protected]',
    url='http://blog.starmeow.cn',
    ext_modules=cythonize(['t_test.py', 'app/t_core.py', 'app/t_main.py'], language_level=3),  # 指定模块列表,language_level使用Py3,具体未查证
)

for file in os.popen("tree /f"):
    print(file, end='')

程序所在目录可以为普通文件夹,也可以为Python包,但子文件夹必须为Python包,也就是存在__init__.py文件
否则会影响output正常值输出。

python setup.py build_ext
  • pyd位置:当前目录名/build/lib.win-amd64-版本/当前目录名/原结构/
  • 临时文件:当前目录名/build/temp.win-amd64-版本/Release/
> python setup.py build_ext

EncryptTest.
│  run.py
│  setup.py
│  t_test.c
│  t_test.py
│  __init__.py
│
├─app
│  │  t_core.c
│  │  t_core.py
│  │  t_main.c
│  │  t_main.py
│  │  __init__.py
│  │
│  └─__pycache__
│          t_core.cpython-37.pyc
│          t_main.cpython-37.pyc
│          __init__.cpython-37.pyc
│
└─build
    ├─lib.win-amd64-3.7
    │  └─EncryptTest
    │      │  t_test.cp37-win_amd64.pyd
    │      │
    │      └─app
    │              t_core.cp37-win_amd64.pyd
    │              t_main.cp37-win_amd64.pyd
    │
    └─temp.win-amd64-3.7
        └─Release
            │  t_test.cp37-win_amd64.exp
            │  t_test.cp37-win_amd64.lib
            │  t_test.obj
            │
            └─app
                    t_core.cp37-win_amd64.exp
                    t_core.cp37-win_amd64.lib
                    t_core.obj
                    t_main.cp37-win_amd64.exp
                    t_main.cp37-win_amd64.lib
                    t_main.obj

# 所有文件都放在build中
python setup.py build_ext --inplace
  • pyd位置:当前目录名/当前目录名/原结构/
  • 临时文件:当前目录名/build/temp.win-amd64-版本/Release/
>python setup.py build_ext --inplace

EncryptTest.
│  run.py
│  setup.py
│  t_test.c
│  t_test.py
│  __init__.py
│
├─app
│  │  t_core.c
│  │  t_core.py
│  │  t_main.c
│  │  t_main.py
│  │  __init__.py
│  │
│  └─__pycache__
│          t_core.cpython-37.pyc
│          t_main.cpython-37.pyc
│          __init__.cpython-37.pyc
│
├─build
│  └─temp.win-amd64-3.7
│      └─Release
│          │  t_test.cp37-win_amd64.exp
│          │  t_test.cp37-win_amd64.lib
│          │  t_test.obj
│          │
│          └─app
│                  t_core.cp37-win_amd64.exp
│                  t_core.cp37-win_amd64.lib
│                  t_core.obj
│                  t_main.cp37-win_amd64.exp
│                  t_main.cp37-win_amd64.lib
│                  t_main.obj
│
└─EncryptTest
    │  t_test.cp37-win_amd64.pyd
    │
    └─app
            t_core.cp37-win_amd64.pyd
            t_main.cp37-win_amd64.pyd

# 编译临时文件放在build中,执行文件放在和当前目录同名的文件夹中
python setup.py build_ext -b “output”
  • pyd位置:当前目录名/-b 指定名称/当前目录名/原结构/
  • 临时文件:当前目录名/build/temp.win-amd64-版本/Release/
>python setup.py build_ext -b "output"

EncryptTest.
│  run.py
│  setup.py
│  t_test.c
│  t_test.py
│  __init__.py
│
├─app
│  │  t_core.c
│  │  t_core.py
│  │  t_main.c
│  │  t_main.py
│  │  __init__.py
│  │
│  └─__pycache__
│          t_core.cpython-37.pyc
│          t_main.cpython-37.pyc
│          __init__.cpython-37.pyc
│
├─build
│  └─temp.win-amd64-3.7
│      └─Release
│          │  t_test.cp37-win_amd64.exp
│          │  t_test.cp37-win_amd64.lib
│          │  t_test.obj
│          │
│          └─app
│                  t_core.cp37-win_amd64.exp
│                  t_core.cp37-win_amd64.lib
│                  t_core.obj
│                  t_main.cp37-win_amd64.exp
│                  t_main.cp37-win_amd64.lib
│                  t_main.obj
│
└─output
    └─EncryptTest
        │  t_test.cp37-win_amd64.pyd
        │
        └─app
                t_core.cp37-win_amd64.pyd
                t_main.cp37-win_amd64.pyd

python setup.py build_ext -b “output” -t “build_tmp”
  • pyd位置:当前目录名/-b 指定名称/当前目录名/原结构/
  • 临时文件:当前目录名/-t 指定名称/Release/
>python setup.py build_ext -b "output" -t "build_tmp"

EncryptTest.
│  run.py
│  setup.py
│  t_test.c
│  t_test.py
│  __init__.py
│
├─app
│  │  t_core.c
│  │  t_core.py
│  │  t_main.c
│  │  t_main.py
│  │  __init__.py
│  │
│  └─__pycache__
│          t_core.cpython-37.pyc
│          t_main.cpython-37.pyc
│          __init__.cpython-37.pyc
│
├─build_tmp
│  └─Release
│      │  t_test.cp37-win_amd64.exp
│      │  t_test.cp37-win_amd64.lib
│      │  t_test.obj
│      │
│      └─app
│              t_core.cp37-win_amd64.exp
│              t_core.cp37-win_amd64.lib
│              t_core.obj
│              t_main.cp37-win_amd64.exp
│              t_main.cp37-win_amd64.lib
│              t_main.obj
│
└─output
    └─EncryptTest
        │  t_test.cp37-win_amd64.pyd
        │
        └─app
                t_core.cp37-win_amd64.pyd
                t_main.cp37-win_amd64.pyd

测试文件夹和包生成编译文件路径区别

测试代码:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

"""
@Version :   Ver1.0
@Author  :   StarMeow
@License :   (C) Copyright 2018-2020, blog.starmeow.cn
@Contact :   [email protected]
@Software:   PyCharm
@File    :   setup.py
@Time    :   2020/2/11 9:54
@Desc    :   测试文件夹和Python包,编译后生成的路径
"""

from distutils.core import setup
from Cython.Build import cythonize
import os

for file in os.popen("tree /f"):
    print(file, end='')

# 编译
try:
    # 批量编译
    setup(
        name="StarMeow app",
        version='0.2',
        description='加密Python源码',
        author='StarMeow',
        author_email='[email protected]',
        url='http://blog.starmeow.cn',
        ext_modules=cythonize(['file_in_a.py', 'b/file_in_b.py', 'c/file_in_c.py'], language_level=3),  # 指定模块列表,language_level使用Py3,具体未查证
        script_args=["build_ext", "-b", "build_output", "-t", "build_tmp"]
    )
except ValueError as e:
    print(e)
# 编译结束

for file in os.popen("tree /f"):
    print(file, end='')

是Python包会在编译的文件夹中生成该包的名称。

1、Directory(Python Package+Python Package)√

常见结构:

a为目录

/a/file

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值