更多内容请点击 我的博客 查看,欢迎来访。
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