Exeprotector/encryptor_secure.py
2025-10-25 17:49:09 +08:00

312 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
安全的EXE加密器 V2 - 使用改进的验证器
作者:太一
"""
import os
import hashlib
import json
import tempfile
import subprocess
import shutil
from typing import Tuple
from cryptography.fernet import Fernet
import base64
class SecureEXEEncryptor:
"""安全的EXE加密器 V2改进版"""
def __init__(self):
self.encryption_key = None
def generate_encryption_key(self) -> bytes:
"""生成加密密钥"""
return Fernet.generate_key()
def encrypt_exe(self, source_path: str, output_path: str,
api_config: dict, software_name: str,
use_v2_validator: bool = True) -> Tuple[bool, str]:
"""
加密EXE文件
参数:
source_path: 原始EXE路径
output_path: 输出路径
api_config: API配置不包含数据库密码
software_name: 软件名称
use_v2_validator: 是否使用V2版本的验证器推荐
"""
try:
print(f"开始加密: {source_path}")
# 1. 读取原始EXE
with open(source_path, 'rb') as f:
original_content = f.read()
print(f"原始文件大小: {len(original_content)} 字节")
# 2. 生成加密密钥
self.encryption_key = self.generate_encryption_key()
# 3. 加密EXE内容
fernet = Fernet(self.encryption_key)
encrypted_content = fernet.encrypt(original_content)
print(f"加密后大小: {len(encrypted_content)} 字节")
# 4. 计算哈希
file_hash = hashlib.sha256(original_content).hexdigest()
# 5. 准备资源数据
key_part1 = base64.b64encode(self.encryption_key[:16]).decode()
key_part2 = base64.b64encode(self.encryption_key[16:]).decode()
resource_data = {
'data': base64.b64encode(encrypted_content).decode(),
'hash': file_hash,
'key_hint': key_part1,
'key_part2': key_part2,
}
# 6. 选择验证器模板
validator_template_path = "validator_secure.py" if use_v2_validator else "validator_secure.py"
if not os.path.exists(validator_template_path):
return False, f"找不到验证器模板: {validator_template_path}"
print(f"使用验证器模板: {validator_template_path}")
with open(validator_template_path, 'r', encoding='utf-8') as f:
template_code = f.read()
# 7. 替换占位符
final_code = template_code.replace(
'# ENCRYPTED_RESOURCE_PLACEHOLDER\nENCRYPTED_RESOURCE = None',
f'ENCRYPTED_RESOURCE = {json.dumps(resource_data)}'
)
# 替换API配置
if 'api_url' in api_config:
final_code = final_code.replace(
'API_BASE_URL = "http://localhost:5100/"',
f'API_BASE_URL = "{api_config["api_url"]}"'
)
if 'api_key' in api_config:
final_code = final_code.replace(
'API_KEY = "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224"',
f'API_KEY = "{api_config["api_key"]}"'
)
# 替换软件名称
final_code = final_code.replace(
'SOFTWARE_NAME = "SOFTWARE_NAME_PLACEHOLDER"',
f'SOFTWARE_NAME = "{software_name}"'
)
# 8. 写入临时Python文件
temp_py = tempfile.mktemp(suffix='.py')
with open(temp_py, 'w', encoding='utf-8') as f:
f.write(final_code)
print(f"生成的Python文件: {temp_py}")
# 9. 编译为EXE
success, msg = self._compile_to_exe(temp_py, output_path)
if success:
# 保存密钥(用于测试和恢复)
key_file = output_path + '.key'
with open(key_file, 'wb') as f:
f.write(self.encryption_key)
print(f"⚠️ 密钥已保存到: {key_file}")
print(f"⚠️ 请妥善保管此文件,建议加密存储!")
# 显示改进说明
if use_v2_validator:
print("\n" + "="*60)
print("✅ 已使用V2验证器包含以下改进")
print(" 1. 持久化缓存存储(不会被系统清理)")
print(" 2. 智能验证策略(减少重复验证)")
print(" 3. 到期提醒功能提前7天开始提醒")
print(" 4. 增强的离线验证")
print(" 5. 改善的用户界面")
print("="*60 + "\n")
# 清理临时文件
try:
os.remove(temp_py)
except:
pass
return success, msg
except Exception as e:
import traceback
error_detail = traceback.format_exc()
print(f"加密过程出错: {error_detail}")
return False, f"加密过程出错: {str(e)}"
def _compile_to_exe(self, python_file: str, output_path: str) -> Tuple[bool, str]:
"""编译Python文件为EXE"""
build_dir = None
try:
# 1. 检查PyInstaller
print("检查 PyInstaller...")
result = subprocess.run(['pyinstaller', '--version'],
capture_output=True, text=True, timeout=10)
if result.returncode != 0:
return False, "PyInstaller未安装或不可用。请运行: pip install pyinstaller"
pyinstaller_version = result.stdout.strip()
print(f"PyInstaller 版本: {pyinstaller_version}")
# 2. 创建工作目录
build_dir = os.path.join(os.path.dirname(os.path.abspath(output_path)), '.build_temp')
os.makedirs(build_dir, exist_ok=True)
print(f"工作目录: {build_dir}")
# 3. 复制Python文件到工作目录
py_name = os.path.basename(python_file)
work_py = os.path.join(build_dir, py_name)
shutil.copy2(python_file, work_py)
print(f"工作文件: {work_py}")
# 4. 构建PyInstaller命令
exe_name = os.path.splitext(os.path.basename(output_path))[0]
cmd = [
'pyinstaller',
'--clean',
'--noconfirm',
'--onefile',
'--windowed',
'--name', exe_name,
'--distpath', build_dir,
'--workpath', os.path.join(build_dir, 'build'),
'--specpath', build_dir,
# 添加隐藏导入
'--hidden-import', 'cryptography.fernet',
'--hidden-import', 'cryptography.hazmat.primitives',
'--hidden-import', 'cryptography.hazmat.backends',
'--hidden-import', 'requests',
'--hidden-import', 'urllib3',
'--hidden-import', 'tkinter',
'--hidden-import', 'tkinter.messagebox',
'--hidden-import', '_tkinter',
'--hidden-import', 'sqlite3',
# 收集所有cryptography包
'--collect-all', 'cryptography',
# 禁用UPX
'--noupx',
work_py
]
print("开始编译...")
print(f"命令: {' '.join(cmd)}")
# 5. 执行编译
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=600,
cwd=build_dir,
encoding='utf-8',
errors='replace'
)
# 6. 输出详细日志
if result.stdout:
print("=== 编译输出 ===")
print(result.stdout[-2000:])
if result.stderr:
print("=== 编译错误 ===")
print(result.stderr[-2000:])
# 7. 检查编译结果
if result.returncode == 0:
compiled_exe = os.path.join(build_dir, f"{exe_name}.exe")
print(f"查找编译文件: {compiled_exe}")
if os.path.exists(compiled_exe):
# 移动到目标位置
os.makedirs(os.path.dirname(output_path), exist_ok=True)
if os.path.exists(output_path):
os.remove(output_path)
shutil.move(compiled_exe, output_path)
print(f"✅ 编译成功: {output_path}")
print(f"文件大小: {os.path.getsize(output_path) / 1024 / 1024:.2f} MB")
# 清理工作目录
try:
shutil.rmtree(build_dir)
print("已清理临时文件")
except:
pass
return True, "编译成功"
else:
return False, f"找不到编译输出文件: {compiled_exe}"
else:
# 提取关键错误信息
error_lines = []
if result.stderr:
error_lines = [line for line in result.stderr.split('\n')
if 'error' in line.lower() or 'failed' in line.lower()]
error_msg = '\n'.join(error_lines[-10:]) if error_lines else result.stderr[-1000:]
return False, f"编译失败 (返回码: {result.returncode})\n{error_msg}"
except subprocess.TimeoutExpired:
return False, "编译超时超过10分钟。请检查系统资源或Python依赖。"
except Exception as e:
import traceback
error_detail = traceback.format_exc()
print(f"编译异常: {error_detail}")
return False, f"编译过程出错: {str(e)}\n\n详细信息:\n{error_detail[-500:]}"
finally:
# 清理临时文件(如果编译失败)
if build_dir and os.path.exists(build_dir):
try:
if not os.path.exists(output_path):
print(f"⚠️ 编译失败,工作目录已保留用于调试: {build_dir}")
except:
pass
# ========== 测试代码 ==========
if __name__ == '__main__':
print("EXE加密器 V2 - 测试")
print("=" * 60)
# 测试加密
encryptor = SecureEXEEncryptor()
# 配置
api_config = {
'api_url': 'http://localhost:5100/',
'api_key': 'taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224'
}
# 示例加密一个测试EXE
# success, msg = encryptor.encrypt_exe(
# source_path='test.exe',
# output_path='test_encrypted_v2.exe',
# api_config=api_config,
# software_name='TestApp',
# use_v2_validator=True # 使用V2验证器
# )
# print(f"\n结果: {success}")
# print(f"消息: {msg}")
print("\n使用说明:")
print("1. 在main.py中使用此加密器")
print("2. 设置 use_v2_validator=True 使用改进版验证器")
print("3. V2验证器包含持久化缓存、到期提醒等改进功能")