Exeprotector/encryptor_secure.py

274 lines
11 KiB
Python
Raw Normal View History

2025-10-23 18:28:10 +08:00
"""
安全的EXE加密器 - 使用AES加密
作者太一
"""
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加密器"""
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) -> Tuple[bool, str]:
"""
加密EXE文件
参数:
source_path: 原始EXE路径
output_path: 输出路径
api_config: API配置不包含数据库密码
software_name: 软件名称
"""
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 not os.path.exists(validator_template_path):
return False, 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)}'
)
final_code = final_code.replace(
'API_BASE_URL = "https://your-server.com/api"',
f'API_BASE_URL = "{api_config.get("api_url", "https://your-server.com/api")}"'
)
final_code = final_code.replace(
'API_KEY = "your-secure-api-key-here"',
f'API_KEY = "{api_config.get("api_key", "your-secure-api-key-here")}"'
)
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"⚠️ 请妥善保管此文件,建议加密存储!")
return success, msg
except Exception as e:
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. 使用简化的命令行方式(更稳定)
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, # spec文件目录
# 添加隐藏导入
'--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',
# 收集所有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, # 10分钟超时
cwd=build_dir,
encoding='utf-8',
errors='replace'
)
# 6. 输出详细日志
if result.stdout:
print("=== 编译输出 ===")
print(result.stdout[-2000:]) # 最后2000字符
if result.stderr:
print("=== 编译错误 ===")
print(result.stderr[-2000:]) # 最后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__':
# 测试加密
encryptor = SecureEXEEncryptor()
# 配置
api_config = {
'api_url': 'https://your-server.com/api',
'api_key': 'your-secure-api-key-here'
}
# 加密
success, msg = encryptor.encrypt_exe(
source_path='test.exe',
output_path='test_encrypted.exe',
api_config=api_config,
software_name='TestApp'
)
print(f"结果: {success} - {msg}")