""" 安全的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}")