Compare commits
6
Commits
07c2a5bf8f
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa836f9453 | ||
|
|
1fdb5bd1bf | ||
|
|
0388984080 | ||
|
|
e7e2a67ea9 | ||
|
|
5ae2a902b1 | ||
|
|
ac766a79b6 |
+4
-1
@@ -11,4 +11,7 @@ wheels/
|
|||||||
.python-version
|
.python-version
|
||||||
output.md
|
output.md
|
||||||
log/
|
log/
|
||||||
*.mb
|
*.mb
|
||||||
|
*.mbank
|
||||||
|
dual_protection_
|
||||||
|
*.db
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
3.13
|
3.12
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
# Python Module Bank - SQLite 模块打包系统
|
# Python Module Bank - SQLite 模块打包系统
|
||||||
|
|
||||||
一个将Python模块打包到SQLite数据库,并支持从数据库直接导入模块的工具系统。
|
一个将 Python 模块打包到 SQLite 数据库,并支持从数据库直接导入模块的工具系统。支持 **AES 字节码加密** 和 **PyArmor 代码混淆** 双重保护机制。
|
||||||
|
|
||||||
## 🌟 特性
|
## 🌟 特性
|
||||||
|
|
||||||
- **单文件分发** - 将所有模块打包到单个SQLite数据库文件
|
- **单文件分发** - 将所有模块打包到单个 SQLite 数据库文件
|
||||||
- **源代码保护** - 模块以编译后的字节码形式存储
|
- **AES 字节码加密** - 编译后的字节码使用 Fernet (AES-128-CBC) 加密存储
|
||||||
- **动态导入** - 运行时直接从数据库加载模块,无需文件系统
|
- **PyArmor 代码混淆** - 支持使用 PyArmor 对 Python 源代码进行混淆保护
|
||||||
|
- **双层保护** - 可同时使用 PyArmor 混淆 + AES 字节码加密,提供双重安全保障
|
||||||
|
- **动态导入** - 运行时直接从数据库加载模块,无缝集成 Python 导入系统
|
||||||
- **完整包支持** - 支持包结构和子模块导入
|
- **完整包支持** - 支持包结构和子模块导入
|
||||||
- **CLI工具** - 提供完整的命令行接口
|
- **CLI 工具** - 提供完整的命令行接口
|
||||||
- **导入钩子** - 无缝集成Python导入系统
|
|
||||||
|
|
||||||
## 📦 安装
|
## 📦 安装
|
||||||
|
|
||||||
@@ -21,63 +22,154 @@ cd module-bank
|
|||||||
pip install -e .
|
pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
### 依赖要求
|
### 使用 uv(推荐)
|
||||||
|
|
||||||
- Python 3.7+
|
```bash
|
||||||
- 无需额外依赖(仅使用标准库)
|
git clone http://124.71.68.6:3000/chakcy/module_bank.git
|
||||||
|
cd module-bank
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### 可选依赖
|
||||||
|
|
||||||
|
- **PyArmor** (强烈推荐): `uv add pyarmor` 或 `pip install pyarmor`
|
||||||
|
- 为模块提供 Python 源代码级别的混淆保护
|
||||||
|
- 商业版可去除试用版水印
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
|
|
||||||
### 1. 创建示例模块
|
### 1. 创建示例模块
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# my_module.py
|
# my_package/__init__.py
|
||||||
def hello():
|
def hello():
|
||||||
print("Hello from my_module!")
|
print("Hello from my_package!")
|
||||||
return "success"
|
return "success"
|
||||||
|
|
||||||
|
VERSION = "1.0.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# my_package/utils.py
|
||||||
|
def add(a, b):
|
||||||
|
return a + b
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 打包模块到数据库
|
### 2. 打包模块到数据库
|
||||||
|
|
||||||
```python
|
#### 使用 AES 加密(推荐)
|
||||||
# pack_example.py
|
|
||||||
from module_bank import PythonToSQLite
|
|
||||||
|
|
||||||
packer = PythonToSQLite("my_modules.db")
|
```bash
|
||||||
packer.pack_module("my_module.py", "my_module")
|
# 生成密钥并打包
|
||||||
packer.pack_directory("my_package/")
|
mbank pack my_package --db my_package.mbank --key "$(python -c "from module_bank.encryption import Encryption; print(Encryption.generate_key())")"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用 PyArmor 混淆
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 PyArmor 保护源代码
|
||||||
|
mbank pack my_package --db my_package.mbank --pyarmor
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用双层保护
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# PyArmor 混淆 + AES 加密
|
||||||
|
mbank pack my_package --db my_package.mbank --pyarmor --key "your-encryption-key"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 从数据库导入
|
### 3. 从数据库导入
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from module_bank import PythonToSQLite
|
# import_example.py
|
||||||
|
from module_bank.python_to_sqlite import PythonToSQLite
|
||||||
|
|
||||||
# 安装导入器
|
packer = PythonToSQLite("my_package.mbank")
|
||||||
packer = PythonToSQLite("my_modules.db")
|
finder = packer.install_importer(key="your-encryption-key") # 如果加密了需要提供密钥
|
||||||
finder = packer.install_importer()
|
|
||||||
|
|
||||||
# 现在可以从数据库导入模块了!
|
from my_package import utils
|
||||||
import my_module
|
from my_package import hello
|
||||||
import my_package.package_module
|
|
||||||
|
|
||||||
my_module.hello()
|
print(hello())
|
||||||
my_package.package_module.hello()
|
print(utils.add(3, 4))
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛡️ 安全保护机制
|
||||||
|
|
||||||
|
### 第一层:PyArmor 代码混淆
|
||||||
|
|
||||||
|
PyArmor 对 Python 源代码进行混淆,将代码转换为难以阅读和逆向的形式:
|
||||||
|
|
||||||
|
- 混淆后的代码依赖运行时二进制文件(`.pyd`/`.so`),无法直接运行
|
||||||
|
- 支持混淆级别控制(代码混淆、模块混淆)
|
||||||
|
- 运行时文件会自动存储在数据库中,加载时自动部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 高级混淆选项
|
||||||
|
mbank pack my_module.py --name my_module --db modules.db \
|
||||||
|
--pyarmor \
|
||||||
|
--pyarmor-obf-code 1 \
|
||||||
|
--pyarmor-obf-mod 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第二层:AES 字节码加密
|
||||||
|
|
||||||
|
编译后的字节码使用 Fernet (AES-128-CBC) 加密:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 生成密钥
|
||||||
|
python -c "from module_bank.encryption import Encryption; print(Encryption.generate_key())" > my_key.key
|
||||||
|
|
||||||
|
# 使用密钥打包
|
||||||
|
mbank pack my_module.py --name my_module --db modules.db --key "$(cat my_key.key)"
|
||||||
|
|
||||||
|
# 使用时提供密钥
|
||||||
|
mbank install --db modules.db --key "$(cat my_key.key)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 安全建议
|
||||||
|
|
||||||
|
1. **密钥管理**:加密密钥不要硬编码在代码中,使用环境变量或密钥管理服务
|
||||||
|
2. **删除源代码**:打包后可从数据库删除源代码,仅保留混淆后的代码:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 删除所有模块的源代码
|
||||||
|
mbank delete_source --db modules.db
|
||||||
|
|
||||||
|
# 删除指定模块的源代码
|
||||||
|
mbank delete_source --db modules.db --module my_module
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📖 详细使用
|
## 📖 详细使用
|
||||||
|
|
||||||
### 命令行工具
|
### 命令行工具
|
||||||
|
|
||||||
```python
|
```bash
|
||||||
# 打包模块或目录
|
# 打包单个模块
|
||||||
mb pack my_package --db modules.db
|
mbank pack my_module.py --name my_module --db modules.db
|
||||||
|
|
||||||
|
# 打包整个目录(自动识别包结构)
|
||||||
|
mbank pack my_package/ --db modules.db
|
||||||
|
|
||||||
|
# 使用 PyArmor 混淆打包
|
||||||
|
mbank pack my_module.py --name my_module --db modules.db --pyarmor
|
||||||
|
|
||||||
|
# 使用 AES 加密打包
|
||||||
|
mbank pack my_module.py --name my_module --db modules.db --key "your-key"
|
||||||
|
|
||||||
|
# 双层保护
|
||||||
|
mbank pack my_module.py --name my_module --db modules.db --pyarmor --key "your-key"
|
||||||
|
|
||||||
# 列出数据库中的模块
|
# 列出数据库中的模块
|
||||||
mb list --db modules.db
|
mbank list --db modules.db
|
||||||
|
|
||||||
|
# 删除源代码
|
||||||
|
mbank delete_source --db modules.db
|
||||||
|
mbank delete_source --db modules.db --module my_module
|
||||||
|
|
||||||
# 安装导入器并进入交互模式
|
# 安装导入器并进入交互模式
|
||||||
mb install --db modules.db
|
mbank install --db modules.db
|
||||||
|
mbank install --db modules.db --key "your-key"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 编程接口
|
### 编程接口
|
||||||
@@ -86,8 +178,21 @@ mb install --db modules.db
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from module_bank import PythonToSQLite
|
from module_bank import PythonToSQLite
|
||||||
|
from module_bank.encryption import Encryption
|
||||||
|
|
||||||
packer = PythonToSQLite("modules.db")
|
# 生成加密密钥
|
||||||
|
key = Encryption.generate_key()
|
||||||
|
|
||||||
|
# 使用 AES 加密打包
|
||||||
|
packer = PythonToSQLite("modules.mbank", key=key)
|
||||||
|
|
||||||
|
# 或使用 PyArmor 混淆打包
|
||||||
|
packer = PythonToSQLite(
|
||||||
|
"modules.mbank",
|
||||||
|
pyarmor_obfuscate=True,
|
||||||
|
pyarmor_obf_code=1,
|
||||||
|
pyarmor_obf_mod=1,
|
||||||
|
)
|
||||||
|
|
||||||
# 打包单个模块
|
# 打包单个模块
|
||||||
packer.pack_module("module.py", "module_name")
|
packer.pack_module("module.py", "module_name")
|
||||||
@@ -103,21 +208,25 @@ packer.verify_package_structure()
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from module_bank import PythonToSQLite
|
from module_bank import PythonToSQLite
|
||||||
import sys
|
|
||||||
|
|
||||||
packer = PythonToSQLite("modules.db")
|
# 如果数据库中的模块被 AES 加密了,需要提供密钥
|
||||||
|
packer = PythonToSQLite("modules.mbank")
|
||||||
|
|
||||||
# 安装导入器到sys.meta_path
|
# 安装导入器到 sys.meta_path
|
||||||
finder = packer.install_importer()
|
finder = packer.install_importer(key="your-key")
|
||||||
|
|
||||||
# 列出所有可用模块
|
# 列出所有可用模块
|
||||||
modules = packer.list_modules()
|
modules = packer.list_modules()
|
||||||
for module in modules:
|
for module in modules:
|
||||||
print(f"{module['module_name']} {'[包]' if module['is_package'] else ''}")
|
package_flag = " [包]" if module["is_package"] else ""
|
||||||
|
print(f" - {module['module_name']}{package_flag}")
|
||||||
|
|
||||||
# 导入数据库中的模块
|
# 直接导入数据库中的模块(就像普通模块一样)
|
||||||
import my_package
|
import my_package
|
||||||
import my_package.submodule
|
import my_package.submodule
|
||||||
|
|
||||||
|
print(my_package)
|
||||||
|
my_package.hello()
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🏗️ 架构设计
|
## 🏗️ 架构设计
|
||||||
@@ -126,35 +235,49 @@ import my_package.submodule
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
src/module_bank/
|
src/module_bank/
|
||||||
├── python_to_sqlite.py # 主打包类
|
├── python_to_sqlite.py # 主打包类
|
||||||
├── sqlite_module_importer.py # 数据库存储管理器
|
├── sqlite_module_importer.py # 数据库存储管理器
|
||||||
├── sqlite_meta_path_finder.py # 元路径查找器
|
├── sqlite_meta_path_finder.py # 元路径查找器
|
||||||
├── sqlite_module_loader.py # 模块加载器
|
├── sqlite_module_loader.py # 模块加载器
|
||||||
├── cli.py # 命令行接口
|
├── encryption.py # AES 密钥管理
|
||||||
└── __init__.py # 模块导出
|
├── pyarmor_encryption.py # PyArmor 混淆封装
|
||||||
|
├── cli.py # 命令行接口
|
||||||
|
└── __init__.py # 模块导出
|
||||||
```
|
```
|
||||||
|
|
||||||
### 数据流
|
### 数据流
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1. 打包阶段:
|
1. 打包阶段:
|
||||||
.py文件 → 编译为字节码 → 存储到SQLite数据库
|
.py 文件 → (可选: PyArmor 混淆) → 编译为字节码 → (可选: AES 加密) → 存储到 SQLite
|
||||||
|
|
||||||
2. 导入阶段:
|
2. 导入阶段:
|
||||||
导入请求 → MetaPathFinder查找 → ModuleLoader加载 → 执行模块
|
导入请求 → MetaPathFinder 查找 → ModuleLoader 加载 → (AES 解密) → (PyArmor 运行时部署) → 执行模块
|
||||||
```
|
```
|
||||||
|
|
||||||
### 数据库模式
|
### 数据库模式
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
|
-- 模块表
|
||||||
CREATE TABLE python_modules (
|
CREATE TABLE python_modules (
|
||||||
module_name TEXT PRIMARY KEY,
|
module_name TEXT PRIMARY KEY,
|
||||||
source_code TEXT, -- 源代码(可选)
|
source_code TEXT, -- 源代码(PyArmor 混淆后)
|
||||||
bytecode BLOB, -- 编译后的字节码
|
bytecode BLOB, -- 编译后的字节码(可选 AES 加密)
|
||||||
is_package BOOLEAN, -- 是否是包
|
is_package BOOLEAN, -- 是否是包
|
||||||
metadata TEXT, -- 元数据(JSON格式)
|
metadata TEXT, -- 元数据(JSON 格式)
|
||||||
|
encrypted INTEGER DEFAULT 0, -- AES 是否已加密
|
||||||
|
pyarmor_protected INTEGER DEFAULT 0, -- 是否已 PyArmor 混淆
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
)
|
);
|
||||||
|
|
||||||
|
-- PyArmor 运行时存储表
|
||||||
|
CREATE TABLE pyarmor_runtime (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
runtime_name TEXT UNIQUE NOT NULL,
|
||||||
|
pyd_data BLOB, -- 原生二进制文件(.pyd/.so)
|
||||||
|
init_data TEXT, -- __init__.py 内容
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔧 高级功能
|
## 🔧 高级功能
|
||||||
@@ -165,7 +288,7 @@ CREATE TABLE python_modules (
|
|||||||
# 打包时排除特定文件
|
# 打包时排除特定文件
|
||||||
packer.pack_directory(
|
packer.pack_directory(
|
||||||
"my_project/",
|
"my_project/",
|
||||||
exclude_patterns=["*_test.py", "*.pyc", "__pycache__"]
|
exclude_patterns=["*_test.py", "*.pyc", "__pycache__"],
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -177,42 +300,56 @@ packer.importer.add_module(
|
|||||||
"my_module",
|
"my_module",
|
||||||
source_code,
|
source_code,
|
||||||
is_package=False,
|
is_package=False,
|
||||||
metadata={"version": "1.0", "author": "me"}
|
metadata={"version": "1.0", "author": "me"},
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 混合导入
|
### 混合导入
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 可以同时使用文件系统和数据库导入
|
# 数据库导入器默认插入到 meta_path 开头,优先级高于文件系统
|
||||||
# 数据库导入器优先级更高
|
|
||||||
import sys
|
import sys
|
||||||
from module_bank import PythonToSQLite
|
from module_bank import PythonToSQLite
|
||||||
|
|
||||||
packer = PythonToSQLite("modules.db")
|
packer = PythonToSQLite("modules.db")
|
||||||
finder = packer.install_importer() # 插入到meta_path开头
|
finder = packer.install_importer()
|
||||||
|
|
||||||
# 如果需要文件系统优先,可以调整插入位置
|
# 如果需要文件系统优先,将查找器追加到末尾
|
||||||
|
sys.meta_path.remove(finder)
|
||||||
sys.meta_path.append(finder)
|
sys.meta_path.append(finder)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 验证运行时完整性
|
||||||
|
|
||||||
|
```python
|
||||||
|
from module_bank.sqlite_module_importer import SQLiteModuleImporter
|
||||||
|
|
||||||
|
importer = SQLiteModuleImporter("modules.db")
|
||||||
|
runtimes = importer.list_runtimes()
|
||||||
|
for rt in runtimes:
|
||||||
|
print(f"运行时: {rt['runtime_name']} (创建于: {rt['created_at']})")
|
||||||
|
```
|
||||||
|
|
||||||
## ⚠️ 注意事项
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
### 安全性
|
### 安全性
|
||||||
|
|
||||||
- 模块字节码直接执行,确保数据库来源可信
|
- 模块字节码直接执行,确保数据库来源可信
|
||||||
- 生产环境建议添加代码签名验证
|
- AES 加密密钥必须安全保管,丢失后将无法解密模块
|
||||||
|
- PyArmor 混淆提供源代码级别的保护,但商业版才能去除试用版水印
|
||||||
|
- 生产环境建议 PyArmor 混淆 + AES 加密同时使用
|
||||||
|
|
||||||
### 兼容性
|
### 兼容性
|
||||||
|
|
||||||
- 字节码不跨Python版本兼容
|
- 字节码不跨 Python 版本兼容(3.12 编译的模块无法在 3.11 加载)
|
||||||
- 不支持C扩展模块
|
- 不支持 C 扩展模块
|
||||||
- 不支持需要文件系统资源的模块(如__file__依赖)
|
- PyArmor 运行时文件与操作系统相关(Windows 用 `.pyd`,Linux 用 `.so`)
|
||||||
|
- 运行时文件需在目标系统上重新打包
|
||||||
|
|
||||||
### 性能
|
### 性能
|
||||||
|
|
||||||
- **启动时**:有一次性数据库查询和反序列化开销
|
- **启动时**:有一次性数据库查询和反序列化/运行时部署开销
|
||||||
- **运行时**:与传统导入性能相同(使用sys.modules缓存)
|
- **运行时**:与传统导入性能相同(使用 sys.modules 缓存)
|
||||||
- **最佳适用**:长期运行的服务、桌面应用
|
- **最佳适用**:长期运行的服务、桌面应用
|
||||||
|
|
||||||
### 更新模块
|
### 更新模块
|
||||||
@@ -233,19 +370,23 @@ DELETE FROM python_modules WHERE module_name = 'module_to_remove';
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 数据库是单个文件,易于备份
|
# 数据库是单个文件,易于备份
|
||||||
cp modules.db modules.backup.db
|
cp modules.mbank modules.backup.mbank
|
||||||
|
|
||||||
# 恢复
|
# 恢复
|
||||||
cp modules.backup.db modules.db
|
cp modules.backup.mbank modules.mbank
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📚 应用场景
|
## 📚 应用场景
|
||||||
|
|
||||||
1. 商业软件分发 - 保护源代码知识产权
|
1. **商业软件分发** - PyArmor 混淆保护源代码知识产权
|
||||||
2. 插件系统 - 动态加载数据库中的插件模块
|
2. **安全敏感环境** - AES 加密确保即使数据库泄露也无法还原代码
|
||||||
3. 教育平台 - 安全分发练习代码
|
3. **插件系统** - 动态加载数据库中的加密/混淆插件模块
|
||||||
4. 微服务 - 打包多个服务模块到单个文件
|
4. **教育平台** - 安全分发练习代码,防止学生抄袭
|
||||||
5. 嵌入式系统 - 减少文件系统依赖
|
5. **微服务** - 打包多个服务模块到单个加密文件
|
||||||
|
6. **嵌入式系统** - 减少文件系统依赖,提高安全性
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**注意**: 本工具主要用于模块分发和部署场景,不适合开发阶段的频繁修改。
|
**注意**: 本工具主要用于模块分发和部署场景,不适合开发阶段的频繁修改。
|
||||||
|
|
||||||
|
**许可证**: MIT License
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
"""
|
||||||
|
PyArmor + AES 双层保护完整示例
|
||||||
|
|
||||||
|
本示例演示如何同时使用 PyArmor 混淆和 AES 字节码加密
|
||||||
|
来提供双重保护。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# 添加项目源码路径
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
# ========== 1. 创建一个需要保护的模块 ==========
|
||||||
|
|
||||||
|
SECRET_MODULE_CODE = """
|
||||||
|
# 这是需要保护的敏感代码(支付逻辑、授权验证等)
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
|
||||||
|
SECRET_KEY = "this-is-a-top-secret-key"
|
||||||
|
|
||||||
|
def verify_license(license_key: str) -> bool:
|
||||||
|
\"\"\"验证许可证密钥(敏感业务逻辑)\"\"\"
|
||||||
|
expected = hmac.new(
|
||||||
|
SECRET_KEY.encode(),
|
||||||
|
license_key.encode(),
|
||||||
|
hashlib.sha256
|
||||||
|
).hexdigest()
|
||||||
|
return expected.startswith("a1b2")
|
||||||
|
|
||||||
|
def calculate_commission(amount: float, level: int) -> float:
|
||||||
|
\"\"\"计算佣金(商业机密算法)\"\"\"
|
||||||
|
rates = {1: 0.1, 2: 0.15, 3: 0.25, 4: 0.35}
|
||||||
|
rate = rates.get(level, 0.05)
|
||||||
|
# 敏感计算公式
|
||||||
|
commission = amount * rate * (1.0 - 0.05)
|
||||||
|
return round(commission, 2)
|
||||||
|
|
||||||
|
class PaymentProcessor:
|
||||||
|
\"\"\"支付处理器(核心商业逻辑)\"\"\"
|
||||||
|
def __init__(self, merchant_id: str):
|
||||||
|
self.merchant_id = merchant_id
|
||||||
|
|
||||||
|
def process(self, order_id: str, amount: float) -> dict:
|
||||||
|
# 模拟支付处理
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"order_id": order_id,
|
||||||
|
"amount": amount,
|
||||||
|
"merchant": self.merchant_id,
|
||||||
|
"transaction_id": hashlib.sha256(
|
||||||
|
f"{order_id}{self.merchant_id}".encode()
|
||||||
|
).hexdigest()[:16]
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSION = "2.1.0"
|
||||||
|
BUILD = "2026-05-04"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 70)
|
||||||
|
print("PyArmor + AES 双层保护完整示例")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
# 创建临时工作目录
|
||||||
|
# work_dir = tempfile.mkdtemp(prefix="dual_protection_")
|
||||||
|
# db_path = os.path.join(work_dir, "protected_modules.db")
|
||||||
|
db_path = os.path.join("dual_protection_", "protected_modules.db")
|
||||||
|
key_file = os.path.join("dual_protection_", "encryption_key.txt")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# ========== 步骤 1: 引入模块 ==========
|
||||||
|
from module_bank.encryption import Encryption
|
||||||
|
from module_bank.python_to_sqlite import PythonToSQLite
|
||||||
|
|
||||||
|
# ========== 步骤 2: 生成 AES 加密密钥 ==========
|
||||||
|
print("\n[步骤 1/5] 生成 AES 加密密钥...")
|
||||||
|
aes_key = Encryption.generate_key()
|
||||||
|
with open(key_file, "w") as f:
|
||||||
|
f.write(aes_key)
|
||||||
|
print(f" AES 密钥已生成: {aes_key[:20]}...")
|
||||||
|
|
||||||
|
# ========== 步骤 3: 使用双层保护打包模块 ==========
|
||||||
|
print("\n[步骤 2/5] 使用 PyArmor + AES 双层保护打包模块...")
|
||||||
|
packer = PythonToSQLite(
|
||||||
|
db_path,
|
||||||
|
key=aes_key, # AES 字节码加密
|
||||||
|
pyarmor_obfuscate=True, # PyArmor 源代码混淆
|
||||||
|
pyarmor_obf_code=1, # 启用代码混淆
|
||||||
|
pyarmor_obf_mod=1, # 启用模块混淆
|
||||||
|
)
|
||||||
|
|
||||||
|
packer.importer.add_module(
|
||||||
|
"payment_system",
|
||||||
|
SECRET_MODULE_CODE,
|
||||||
|
is_package=False,
|
||||||
|
metadata={
|
||||||
|
"version": "2.1.0",
|
||||||
|
"protection": "pyarmor_obfuscation + aes_encryption",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
print(" ✓ 模块已使用双层保护存储到数据库")
|
||||||
|
|
||||||
|
# ========== 步骤 4: 验证数据库中的保护状态 ==========
|
||||||
|
print("\n[步骤 3/5] 验证数据库中的保护状态...")
|
||||||
|
cursor = packer.importer.connection.execute(
|
||||||
|
"""SELECT module_name, encrypted, pyarmor_protected, source_code,
|
||||||
|
LENGTH(bytecode) as bytecode_size,
|
||||||
|
LENGTH(source_code) as source_size
|
||||||
|
FROM python_modules"""
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
print(f" 模块名: {row['module_name']}")
|
||||||
|
print(f" AES 加密: {'✓ 是' if row['encrypted'] else '✗ 否'}")
|
||||||
|
print(f" PyArmor 混淆: {'✓ 是' if row['pyarmor_protected'] else '✗ 否'}")
|
||||||
|
print(f" 字节码大小: {row['bytecode_size']} 字节")
|
||||||
|
print(f" 源代码大小: {row['source_size']} 字节")
|
||||||
|
|
||||||
|
# 验证 PyArmor 运行时也已存储
|
||||||
|
runtimes = packer.importer.list_runtimes()
|
||||||
|
for rt in runtimes:
|
||||||
|
print(f" PyArmor 运行时: {rt['runtime_name']}")
|
||||||
|
|
||||||
|
# 显示数据库中存储的混淆代码片段
|
||||||
|
print("\n[验证] 数据库中的混淆代码(前200字符):")
|
||||||
|
obfuscated = row["source_code"]
|
||||||
|
print(f" {obfuscated[:200]}...")
|
||||||
|
|
||||||
|
# ========== 步骤 5: 从数据库导入并执行 ==========
|
||||||
|
print("\n[步骤 4/5] 从数据库导入模块(需要提供 AES 密钥)...")
|
||||||
|
# 清除之前的导入
|
||||||
|
if "payment_system" in sys.modules:
|
||||||
|
del sys.modules["payment_system"]
|
||||||
|
|
||||||
|
# 使用密钥安装导入器
|
||||||
|
finder = packer.install_importer(key=aes_key)
|
||||||
|
|
||||||
|
# 导入模块
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
mod = importlib.import_module("payment_system")
|
||||||
|
|
||||||
|
print(f" ✓ 模块导入成功: {mod}")
|
||||||
|
print(f" 模块文件: {mod.__file__}")
|
||||||
|
|
||||||
|
# ========== 步骤 6: 验证模块功能正常 ==========
|
||||||
|
print("\n[步骤 5/5] 验证模块功能...")
|
||||||
|
|
||||||
|
# 调用验证许可证函数
|
||||||
|
result = mod.verify_license("TEST-1234-5678")
|
||||||
|
print(f" verify_license('TEST-1234-5678') → {result}")
|
||||||
|
|
||||||
|
# 调用佣金计算
|
||||||
|
commission = mod.calculate_commission(10000.0, 3)
|
||||||
|
print(f" calculate_commission(10000, 3) → ¥{commission}")
|
||||||
|
|
||||||
|
# 使用支付处理器
|
||||||
|
processor = mod.PaymentProcessor("MERCHANT_001")
|
||||||
|
payment_result = processor.process("ORDER_20260504", 299.00)
|
||||||
|
print(f" PaymentProcessor.process() → {payment_result['status']}")
|
||||||
|
print(f" 交易ID: {payment_result['transaction_id']}")
|
||||||
|
|
||||||
|
# 访问模块常量
|
||||||
|
print(f"\n 模块版本: {mod.VERSION}")
|
||||||
|
print(f" 构建日期: {mod.BUILD}")
|
||||||
|
|
||||||
|
# ========== 安全验证 ==========
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("安全验证")
|
||||||
|
print("=" * 70)
|
||||||
|
print("""
|
||||||
|
1. PyArmor 混淆: 源代码已转换为不可读的混淆代码
|
||||||
|
2. AES 加密: 字节码已使用 AES-128-CBC 加密
|
||||||
|
3. 运行时保护: PyArmor 运行时二进制文件无法直接加载
|
||||||
|
4. 双重解密: 需要先 AES 解密 → 再通过 PyArmor 运行时执行
|
||||||
|
|
||||||
|
即使有人获取了数据库文件:
|
||||||
|
- 没有 AES 密钥 → 无法解密字节码
|
||||||
|
- 没有 PyArmor 运行时 → 无法执行混淆代码
|
||||||
|
- 两者同时具备 → 才能解密和执行
|
||||||
|
""")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 错误: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
# 清理
|
||||||
|
print("\n[清理] 清除临时文件...")
|
||||||
|
# shutil.rmtree(work_dir, ignore_errors=True)
|
||||||
|
if "payment_system" in sys.modules:
|
||||||
|
del sys.modules["payment_system"]
|
||||||
|
print(" 完成!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
from module_bank import PythonToSQLite
|
||||||
|
|
||||||
|
with open("./dual_protection_/encryption_key.txt") as f:
|
||||||
|
key = f.read()
|
||||||
|
|
||||||
|
packer = PythonToSQLite(
|
||||||
|
"./dual_protection_/protected_modules.db",
|
||||||
|
key=key,
|
||||||
|
pyarmor_obfuscate=True,
|
||||||
|
pyarmor_obf_code=1,
|
||||||
|
pyarmor_obf_mod=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
packer.install_importer()
|
||||||
|
|
||||||
|
import payment_system # type: ignore
|
||||||
|
|
||||||
|
mod = payment_system
|
||||||
|
print(f" ✓ 模块导入成功: {mod}")
|
||||||
|
print(f" 模块文件: {mod.__file__}")
|
||||||
|
|
||||||
|
# ========== 步骤 6: 验证模块功能正常 ==========
|
||||||
|
print("\n[步骤 5/5] 验证模块功能...")
|
||||||
|
|
||||||
|
# 调用验证许可证函数
|
||||||
|
result = mod.verify_license("TEST-1234-5678")
|
||||||
|
print(f" verify_license('TEST-1234-5678') → {result}")
|
||||||
|
|
||||||
|
# 调用佣金计算
|
||||||
|
commission = mod.calculate_commission(10000.0, 3)
|
||||||
|
print(f" calculate_commission(10000, 3) → ¥{commission}")
|
||||||
|
|
||||||
|
# 使用支付处理器
|
||||||
|
processor = mod.PaymentProcessor("MERCHANT_001")
|
||||||
|
payment_result = processor.process("ORDER_20260504", 299.00)
|
||||||
|
print(f" PaymentProcessor.process() → {payment_result['status']}")
|
||||||
|
print(f" 交易ID: {payment_result['transaction_id']}")
|
||||||
|
|
||||||
|
# 访问模块常量
|
||||||
|
print(f"\n 模块版本: {mod.VERSION}")
|
||||||
|
print(f" 构建日期: {mod.BUILD}")
|
||||||
|
|
||||||
|
# ========== 安全验证 ==========
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("安全验证")
|
||||||
|
print("=" * 70)
|
||||||
|
print("""
|
||||||
|
1. PyArmor 混淆: 源代码已转换为不可读的混淆代码
|
||||||
|
2. AES 加密: 字节码已使用 AES-128-CBC 加密
|
||||||
|
3. 运行时保护: PyArmor 运行时二进制文件无法直接加载
|
||||||
|
4. 双重解密: 需要先 AES 解密 → 再通过 PyArmor 运行时执行
|
||||||
|
|
||||||
|
即使有人获取了数据库文件:
|
||||||
|
- 没有 AES 密钥 → 无法解密字节码
|
||||||
|
- 没有 PyArmor 运行时 → 无法执行混淆代码
|
||||||
|
- 两者同时具备 → 才能解密和执行
|
||||||
|
""")
|
||||||
+12
-4
@@ -1,10 +1,13 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "module_bank"
|
dependencies = [
|
||||||
version = "0.1.0"
|
"cryptography>=45.0.7",
|
||||||
|
"pyarmor>=9.2.4",
|
||||||
|
]
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
|
name = "module_bank"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.7"
|
requires-python = ">=3.7"
|
||||||
dependencies = []
|
version = "0.3.1"
|
||||||
|
|
||||||
[[tool.uv.index]]
|
[[tool.uv.index]]
|
||||||
default = true
|
default = true
|
||||||
@@ -14,8 +17,13 @@ url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
|||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"twine>=4.0.2",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["src/module_bank"]
|
packages = ["src/module_bank"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
mb = "module_bank.cli:main"
|
mbank = "module_bank.cli:main"
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
"""
|
||||||
|
测试:删除 PyArmor 模块的源代码后仍能正常加载
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
work_dir = tempfile.mkdtemp(prefix="test_delete_source_")
|
||||||
|
db_path = os.path.join(work_dir, "test.db")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from module_bank.python_to_sqlite import PythonToSQLite
|
||||||
|
|
||||||
|
# 1. 创建并打包 PyArmor 模块
|
||||||
|
print("=" * 60)
|
||||||
|
print("测试:删除源代码后加载 PyArmor 模块")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
print("\n[1/5] 使用 PyArmor 打包模块...")
|
||||||
|
packer = PythonToSQLite(db_path, pyarmor_obfuscate=True)
|
||||||
|
packer.importer.add_module(
|
||||||
|
"mymod",
|
||||||
|
'def hello(): return "ok"',
|
||||||
|
is_package=False,
|
||||||
|
)
|
||||||
|
print(" 模块打包完成")
|
||||||
|
|
||||||
|
# 2. 验证 metadata 中已记录运行时名
|
||||||
|
print("\n[2/5] 验证 metadata 包含运行时名...")
|
||||||
|
cursor = packer.importer.connection.execute(
|
||||||
|
"SELECT metadata, source_code FROM python_modules WHERE module_name = ?",
|
||||||
|
("mymod",),
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
print(f" metadata: {row['metadata']}")
|
||||||
|
assert "pyarmor_runtime" in row["metadata"], "metadata 缺少运行时名"
|
||||||
|
print(" ✓ metadata 正确包含运行时名")
|
||||||
|
assert row["source_code"] is not None, "源代码不应为空"
|
||||||
|
print(" ✓ 源代码存在")
|
||||||
|
|
||||||
|
# 3. 导入一次(确保能工作)
|
||||||
|
print("\n[3/5] 导入模块(有源代码)...")
|
||||||
|
from module_bank.sqlite_meta_path_finder import SQLiteMetaPathFinder
|
||||||
|
|
||||||
|
finder = SQLiteMetaPathFinder(db_path)
|
||||||
|
sys.meta_path.insert(0, finder)
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
mod = importlib.import_module("mymod")
|
||||||
|
assert mod.hello() == "ok"
|
||||||
|
print(f" ✓ 导入成功: hello() = {mod.hello()}")
|
||||||
|
|
||||||
|
# 4. 删除源代码
|
||||||
|
print("\n[4/5] 删除源代码...")
|
||||||
|
packer.delete_source_code("mymod")
|
||||||
|
cursor = packer.importer.connection.execute(
|
||||||
|
"SELECT source_code, metadata FROM python_modules WHERE module_name = ?",
|
||||||
|
("mymod",),
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
print(f" 删除后 source_code = {row['source_code']}")
|
||||||
|
assert row["source_code"] is None, "源代码未成功删除"
|
||||||
|
print(f" 删除后 metadata = {row['metadata']}")
|
||||||
|
assert row["metadata"] is not None, "metadata 不应为空"
|
||||||
|
print(" ✓ metadata 仍保留")
|
||||||
|
|
||||||
|
# 5. 重新导入(无源代码)
|
||||||
|
print("\n[5/5] 重新导入模块(无源代码)...")
|
||||||
|
if "mymod" in sys.modules:
|
||||||
|
del sys.modules["mymod"]
|
||||||
|
|
||||||
|
mod2 = importlib.import_module("mymod")
|
||||||
|
result = mod2.hello()
|
||||||
|
print(f" ✓ 无源代码导入成功: hello() = {result}")
|
||||||
|
assert result == "ok", "模块功能异常"
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("全部通过!删除源代码后仍可正常加载 PyArmor 模块")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 测试失败: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return 1
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(work_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
"""
|
||||||
|
集成测试:测试 PyArmor 加密打包 -> 存储 -> 加载 -> 执行 全流程
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# 添加项目模块路径
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
TEST_DB = os.path.join(os.path.dirname(__file__), "test_pyarmor_integration.db")
|
||||||
|
|
||||||
|
|
||||||
|
def create_test_module():
|
||||||
|
"""创建测试用的 Python 模块"""
|
||||||
|
return """
|
||||||
|
def greet(name):
|
||||||
|
return f"Hello, {name}! This is a PyArmor protected module."
|
||||||
|
|
||||||
|
class Calculator:
|
||||||
|
def add(self, a, b):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
def multiply(self, a, b):
|
||||||
|
return a * b
|
||||||
|
|
||||||
|
VERSION = "1.0.0"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_pyarmor_obfuscate():
|
||||||
|
"""测试 PyArmor 混淆功能"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("测试 1: PyArmor 混淆功能")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
from module_bank.pyarmor_encryption import PyArmorEncryption
|
||||||
|
|
||||||
|
enc = PyArmorEncryption(obf_code=1, obf_mod=1)
|
||||||
|
|
||||||
|
# 检查 PyArmor 是否可用
|
||||||
|
if not enc.check_pyarmor_available():
|
||||||
|
print("ERROR: PyArmor 未安装或不可用")
|
||||||
|
return False
|
||||||
|
|
||||||
|
source = create_test_module()
|
||||||
|
print(f"原始代码长度: {len(source)} 字节")
|
||||||
|
|
||||||
|
try:
|
||||||
|
obfuscated_code, runtime_data = enc.obfuscate_source(
|
||||||
|
source, "test_protected_module"
|
||||||
|
)
|
||||||
|
print(f"混淆后代码长度: {len(obfuscated_code)} 字节")
|
||||||
|
print(f"运行时文件: {'有' if runtime_data else '无'}")
|
||||||
|
print(f"运行时数据大小: {len(runtime_data) if runtime_data else 0} 字节")
|
||||||
|
|
||||||
|
# 验证是否被保护
|
||||||
|
assert enc.is_pyarmor_protected(obfuscated_code), "代码应被标记为 PyArmor 保护"
|
||||||
|
print(f"PyArmor 保护标记: 正确")
|
||||||
|
|
||||||
|
# 提取运行时模块名
|
||||||
|
runtime_name = enc.extract_runtime_info(obfuscated_code)
|
||||||
|
print(f"运行时模块名: {runtime_name}")
|
||||||
|
|
||||||
|
print("测试 1 通过!\n")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_pack_with_pyarmor():
|
||||||
|
"""测试使用 PyArmor 打包到 SQLite"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("测试 2: 使用 PyArmor 打包到 SQLite")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 清理旧的测试数据库
|
||||||
|
if os.path.exists(TEST_DB):
|
||||||
|
os.remove(TEST_DB)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from module_bank.python_to_sqlite import PythonToSQLite
|
||||||
|
|
||||||
|
# 使用 PyArmor 创建打包器
|
||||||
|
packer = PythonToSQLite(
|
||||||
|
TEST_DB,
|
||||||
|
pyarmor_obfuscate=True,
|
||||||
|
pyarmor_obf_code=1,
|
||||||
|
pyarmor_obf_mod=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
source = create_test_module()
|
||||||
|
packer.importer.add_module("test_protected_module", source, is_package=False)
|
||||||
|
print("模块已打包到数据库")
|
||||||
|
|
||||||
|
# 验证模块已存储
|
||||||
|
modules = packer.list_modules()
|
||||||
|
module_names = [m["module_name"] for m in modules]
|
||||||
|
assert "test_protected_module" in module_names, "模块应在数据库中存在"
|
||||||
|
print(f"数据库中的模块: {module_names}")
|
||||||
|
|
||||||
|
# 验证运行时已存储
|
||||||
|
runtimes = packer.importer.list_runtimes()
|
||||||
|
print(f"数据库中的运行时: {[r['runtime_name'] for r in runtimes]}")
|
||||||
|
|
||||||
|
# 验证 pyarmor_protected 标记
|
||||||
|
cursor = packer.importer.connection.execute(
|
||||||
|
"SELECT pyarmor_protected FROM python_modules WHERE module_name = ?",
|
||||||
|
("test_protected_module",),
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
assert row["pyarmor_protected"] == 1, "模块应标记为 pyarmor_protected"
|
||||||
|
print("PyArmor 保护标记: 已正确设置")
|
||||||
|
|
||||||
|
print("测试 2 通过!\n")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_pyarmor_module():
|
||||||
|
"""测试从 SQLite 导入并执行 PyArmor 保护的模块"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("测试 3: 从 SQLite 导入并执行 PyArmor 保护模块")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from module_bank.python_to_sqlite import PythonToSQLite
|
||||||
|
from module_bank.sqlite_meta_path_finder import SQLiteMetaPathFinder
|
||||||
|
|
||||||
|
packer = PythonToSQLite(TEST_DB)
|
||||||
|
|
||||||
|
# 安装导入器
|
||||||
|
finder = packer.install_importer()
|
||||||
|
|
||||||
|
# 尝试导入模块
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
mod = importlib.import_module("test_protected_module")
|
||||||
|
print(f"模块导入成功: {mod}")
|
||||||
|
print(f"模块名: {mod.__name__}")
|
||||||
|
print(f"模块文件: {mod.__file__}")
|
||||||
|
|
||||||
|
# 调用模块函数
|
||||||
|
result = mod.greet("World")
|
||||||
|
print(f"调用 greet('World'): {result}")
|
||||||
|
assert result == "Hello, World! This is a PyArmor protected module."
|
||||||
|
|
||||||
|
# 使用模块类
|
||||||
|
calc = mod.Calculator()
|
||||||
|
assert calc.add(3, 4) == 7
|
||||||
|
assert calc.multiply(3, 4) == 12
|
||||||
|
print(f"Calculator.add(3, 4) = {calc.add(3, 4)}")
|
||||||
|
print(f"Calculator.multiply(3, 4) = {calc.multiply(3, 4)}")
|
||||||
|
|
||||||
|
# 访问模块变量
|
||||||
|
print(f"VERSION = {mod.VERSION}")
|
||||||
|
assert mod.VERSION == "1.0.0"
|
||||||
|
|
||||||
|
print("测试 3 通过!\n")
|
||||||
|
|
||||||
|
# 清理
|
||||||
|
sys.meta_path.remove(finder)
|
||||||
|
if "test_protected_module" in sys.modules:
|
||||||
|
del sys.modules["test_protected_module"]
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_with_pyarmor():
|
||||||
|
"""测试 CLI 的 PyArmor 参数"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("测试 4: CLI --pyarmor 参数")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
# 创建临时源文件
|
||||||
|
tmpdir = tempfile.mkdtemp()
|
||||||
|
src_file = os.path.join(tmpdir, "hello_module.py")
|
||||||
|
with open(src_file, "w", encoding="utf-8") as f:
|
||||||
|
f.write("def hello():\n return 'Hello from PyArmor CLI!'\n")
|
||||||
|
|
||||||
|
db_path = os.path.join(tmpdir, "test_cli.db")
|
||||||
|
|
||||||
|
# 测试解析 --pyarmor 参数
|
||||||
|
from module_bank.cli import main as cli_main
|
||||||
|
|
||||||
|
# 保存原始argv
|
||||||
|
old_argv = sys.argv
|
||||||
|
try:
|
||||||
|
sys.argv = [
|
||||||
|
"mbank",
|
||||||
|
"pack",
|
||||||
|
src_file,
|
||||||
|
"--db",
|
||||||
|
db_path,
|
||||||
|
"--name",
|
||||||
|
"hello_cli_module",
|
||||||
|
"--pyarmor",
|
||||||
|
]
|
||||||
|
cli_main()
|
||||||
|
print("CLI pack with --pyarmor 执行成功")
|
||||||
|
|
||||||
|
# 验证模块已用 PyArmor 保护
|
||||||
|
from module_bank.sqlite_module_importer import SQLiteModuleImporter
|
||||||
|
|
||||||
|
importer = SQLiteModuleImporter(db_path)
|
||||||
|
cursor = importer.connection.execute(
|
||||||
|
"SELECT pyarmor_protected FROM python_modules WHERE module_name = ?",
|
||||||
|
("hello_cli_module",),
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
assert row and row["pyarmor_protected"] == 1, "CLI 打包的模块应启用 PyArmor"
|
||||||
|
print("CLI 打包的模块已正确启用 PyArmor 保护")
|
||||||
|
|
||||||
|
# 验证运行时存储
|
||||||
|
runtimes = importer.list_runtimes()
|
||||||
|
assert len(runtimes) > 0, "CLI 打包后应有运行时文件存储在数据库中"
|
||||||
|
print(f"运行时已存储: {[r['runtime_name'] for r in runtimes]}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
sys.argv = old_argv
|
||||||
|
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||||
|
|
||||||
|
print("测试 4 通过!\n")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup():
|
||||||
|
"""清理测试文件"""
|
||||||
|
if os.path.exists(TEST_DB):
|
||||||
|
os.remove(TEST_DB)
|
||||||
|
print(f"已清理测试数据库: {TEST_DB}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("PyArmor 集成测试套件")
|
||||||
|
print("=" * 60 + "\n")
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
("PyArmor 混淆功能", test_pyarmor_obfuscate),
|
||||||
|
("PyArmor 打包到 SQLite", test_pack_with_pyarmor),
|
||||||
|
("从 SQLite 导入 PyArmor 模块", test_import_pyarmor_module),
|
||||||
|
("CLI --pyarmor 参数", test_cli_with_pyarmor),
|
||||||
|
]
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for name, func in tests:
|
||||||
|
print(f"\n>>> 运行测试: {name}")
|
||||||
|
try:
|
||||||
|
if func():
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
failed += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"测试异常: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print(f"测试完成: {passed} 通过, {failed} 失败")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
sys.exit(1 if failed > 0 else 0)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Pyarmor 9.2.4 (trial), 000000, 2026-05-04T17:52:33.319452
|
||||||
|
from .pyarmor_runtime import __pyarmor__
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,53 @@
|
|||||||
|
"""测试 PyArmor 加密功能"""
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# 确保能导入项目模块
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_pyarmor_encrypt():
|
||||||
|
"""测试 PyArmor encrypt_script 功能"""
|
||||||
|
import pyarmor.pyarmor as pa
|
||||||
|
|
||||||
|
tmpdir = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
src = os.path.join(tmpdir, "test_mod.py")
|
||||||
|
with open(src, "w", encoding="utf-8") as f:
|
||||||
|
f.write('def hello():\n print("Hello from PyArmor!")\n')
|
||||||
|
|
||||||
|
dest = os.path.join(tmpdir, "dist", "test_mod.py")
|
||||||
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||||
|
|
||||||
|
# Check capsule
|
||||||
|
capsule = pa.DEFAULT_CAPSULE
|
||||||
|
print(f"Capsule: {capsule}")
|
||||||
|
print(f"Capsule exists: {os.path.exists(capsule)}")
|
||||||
|
|
||||||
|
# Try encrypt
|
||||||
|
result = pa.encrypt_script(
|
||||||
|
pubkey=capsule,
|
||||||
|
filename=src,
|
||||||
|
destname=dest,
|
||||||
|
)
|
||||||
|
print(f"Result: {result}")
|
||||||
|
|
||||||
|
# Check output
|
||||||
|
if os.path.exists(dest):
|
||||||
|
with open(dest, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
print(f"Output length: {len(content)} bytes")
|
||||||
|
print(f"First 300 chars:\n{content[:300]}")
|
||||||
|
print(f"\n--- Full content ({len(content)} bytes) ---")
|
||||||
|
print(content)
|
||||||
|
else:
|
||||||
|
print(f"ERROR: Output file not created at {dest}")
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_pyarmor_encrypt()
|
||||||
@@ -2,10 +2,12 @@ from .python_to_sqlite import PythonToSQLite
|
|||||||
from .sqlite_module_importer import SQLiteModuleImporter
|
from .sqlite_module_importer import SQLiteModuleImporter
|
||||||
from .sqlite_module_loader import SQLiteModuleLoader
|
from .sqlite_module_loader import SQLiteModuleLoader
|
||||||
from .sqlite_meta_path_finder import SQLiteMetaPathFinder
|
from .sqlite_meta_path_finder import SQLiteMetaPathFinder
|
||||||
|
from .pyarmor_encryption import PyArmorEncryption
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"PythonToSQLite",
|
"PythonToSQLite",
|
||||||
"SQLiteModuleImporter",
|
"SQLiteModuleImporter",
|
||||||
"SQLiteModuleLoader",
|
"SQLiteModuleLoader",
|
||||||
"SQLiteMetaPathFinder",
|
"SQLiteMetaPathFinder",
|
||||||
|
"PyArmorEncryption",
|
||||||
]
|
]
|
||||||
|
|||||||
+44
-2
@@ -11,6 +11,28 @@ def main():
|
|||||||
pack_parser.add_argument("source", help="源文件或目录")
|
pack_parser.add_argument("source", help="源文件或目录")
|
||||||
pack_parser.add_argument("--db", default="modules.db", help="数据库文件路径")
|
pack_parser.add_argument("--db", default="modules.db", help="数据库文件路径")
|
||||||
pack_parser.add_argument("--name", help="模块名(默认从文件名推断)")
|
pack_parser.add_argument("--name", help="模块名(默认从文件名推断)")
|
||||||
|
pack_parser.add_argument(
|
||||||
|
"--key", help="加密密钥(Fernet格式,32字节base64),若不提供则不加密"
|
||||||
|
)
|
||||||
|
pack_parser.add_argument(
|
||||||
|
"--pyarmor",
|
||||||
|
action="store_true",
|
||||||
|
help="使用 PyArmor 混淆保护源代码",
|
||||||
|
)
|
||||||
|
pack_parser.add_argument(
|
||||||
|
"--pyarmor-obf-code",
|
||||||
|
type=int,
|
||||||
|
default=1,
|
||||||
|
choices=[0, 1],
|
||||||
|
help="PyArmor 代码混淆级别 (0=禁用, 1=启用, 默认: 1)",
|
||||||
|
)
|
||||||
|
pack_parser.add_argument(
|
||||||
|
"--pyarmor-obf-mod",
|
||||||
|
type=int,
|
||||||
|
default=1,
|
||||||
|
choices=[0, 1],
|
||||||
|
help="PyArmor 模块混淆级别 (0=禁用, 1=启用, 默认: 1)",
|
||||||
|
)
|
||||||
|
|
||||||
# 列出命令
|
# 列出命令
|
||||||
list_parser = subparsers.add_parser("list", help="列出数据库中的模块")
|
list_parser = subparsers.add_parser("list", help="列出数据库中的模块")
|
||||||
@@ -28,13 +50,22 @@ def main():
|
|||||||
# 安装命令
|
# 安装命令
|
||||||
install_parser = subparsers.add_parser("install", help="安装SQLite导入器")
|
install_parser = subparsers.add_parser("install", help="安装SQLite导入器")
|
||||||
install_parser.add_argument("--db", default="modules.db", help="数据库文件路径")
|
install_parser.add_argument("--db", default="modules.db", help="数据库文件路径")
|
||||||
|
install_parser.add_argument(
|
||||||
|
"--key", help="解密密钥(Fernet格式),如果数据库中有加密模块则必须提供"
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.command == "pack":
|
if args.command == "pack":
|
||||||
from .python_to_sqlite import PythonToSQLite
|
from .python_to_sqlite import PythonToSQLite
|
||||||
|
|
||||||
packer = PythonToSQLite(args.db)
|
packer = PythonToSQLite(
|
||||||
|
args.db,
|
||||||
|
key=args.key,
|
||||||
|
pyarmor_obfuscate=args.pyarmor,
|
||||||
|
pyarmor_obf_code=args.pyarmor_obf_code,
|
||||||
|
pyarmor_obf_mod=args.pyarmor_obf_mod,
|
||||||
|
)
|
||||||
|
|
||||||
if Path(args.source).is_dir():
|
if Path(args.source).is_dir():
|
||||||
packer.pack_directory(args.source)
|
packer.pack_directory(args.source)
|
||||||
@@ -43,6 +74,17 @@ def main():
|
|||||||
packer.pack_module(args.source, args.name)
|
packer.pack_module(args.source, args.name)
|
||||||
print(f"已打包模块: {args.source}")
|
print(f"已打包模块: {args.source}")
|
||||||
|
|
||||||
|
# 如果启用了 PyArmor,显示运行时信息
|
||||||
|
if args.pyarmor:
|
||||||
|
from .sqlite_module_importer import SQLiteModuleImporter
|
||||||
|
|
||||||
|
importer = SQLiteModuleImporter(args.db)
|
||||||
|
runtimes = importer.list_runtimes()
|
||||||
|
if runtimes:
|
||||||
|
print(f"\nPyArmor 运行时信息:")
|
||||||
|
for rt in runtimes:
|
||||||
|
print(f" - {rt['runtime_name']} (创建于: {rt['created_at']})")
|
||||||
|
|
||||||
elif args.command == "list":
|
elif args.command == "list":
|
||||||
from .python_to_sqlite import PythonToSQLite
|
from .python_to_sqlite import PythonToSQLite
|
||||||
|
|
||||||
@@ -67,7 +109,7 @@ def main():
|
|||||||
from .python_to_sqlite import PythonToSQLite
|
from .python_to_sqlite import PythonToSQLite
|
||||||
|
|
||||||
packer = PythonToSQLite(args.db)
|
packer = PythonToSQLite(args.db)
|
||||||
packer.install_importer()
|
packer.install_importer(key=args.key)
|
||||||
print(f"已安装SQLite导入器,可以从数据库导入模块了!")
|
print(f"已安装SQLite导入器,可以从数据库导入模块了!")
|
||||||
|
|
||||||
# 保持程序运行以便交互使用
|
# 保持程序运行以便交互使用
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from cryptography.fernet import Fernet
|
||||||
|
import base64
|
||||||
|
|
||||||
|
|
||||||
|
class Encryption:
|
||||||
|
def __init__(self, key=None):
|
||||||
|
if key is None:
|
||||||
|
self.cipher = None
|
||||||
|
else:
|
||||||
|
if isinstance(key, str):
|
||||||
|
key = key.encode("utf-8")
|
||||||
|
try:
|
||||||
|
self.cipher = Fernet(key)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"Invalid key: {e}") from e
|
||||||
|
|
||||||
|
def encrypt(self, data):
|
||||||
|
if self.cipher is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = data.encode("utf-8")
|
||||||
|
return self.cipher.encrypt(data)
|
||||||
|
|
||||||
|
def decrypt(self, data):
|
||||||
|
if self.cipher is None:
|
||||||
|
return data
|
||||||
|
return self.cipher.decrypt(data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_key():
|
||||||
|
return Fernet.generate_key().decode("utf-8")
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
"""
|
||||||
|
PyArmor 加密支持模块
|
||||||
|
|
||||||
|
为 module_bank 提供 PyArmor 代码混淆/加密能力。
|
||||||
|
使用 PyArmor CLI 对 Python 源代码进行混淆保护,
|
||||||
|
并将混淆后的源代码和运行时文件存储到 SQLite 数据库中。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
class PyArmorEncryption:
|
||||||
|
"""PyArmor 加密处理器,用于混淆/保护 Python 源代码"""
|
||||||
|
|
||||||
|
def __init__(self, key: Optional[str] = None, obf_code: int = 1, obf_mod: int = 1):
|
||||||
|
"""
|
||||||
|
初始化 PyArmor 加密处理器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: 加密密钥(可选,PyArmor 不使用此参数,保留接口兼容性)
|
||||||
|
obf_code: 代码混淆级别 (0/1)
|
||||||
|
obf_mod: 模块混淆级别 (0/1)
|
||||||
|
"""
|
||||||
|
self.key = key
|
||||||
|
self.obf_code = obf_code
|
||||||
|
self.obf_mod = obf_mod
|
||||||
|
|
||||||
|
def obfuscate_source(
|
||||||
|
self, source_code: str, module_name: str
|
||||||
|
) -> Tuple[str, Optional[dict]]:
|
||||||
|
"""
|
||||||
|
使用 PyArmor 混淆源代码
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_code: 原始 Python 源代码
|
||||||
|
module_name: 模块名
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(混淆后的源代码, PyArmor 运行时数据字典)
|
||||||
|
运行时数据字典包含: {
|
||||||
|
"runtime_name": "pyarmor_runtime_000000",
|
||||||
|
"pyd_data": bytes,
|
||||||
|
"init_data": str,
|
||||||
|
}
|
||||||
|
为 None 表示没有运行时文件
|
||||||
|
"""
|
||||||
|
if not self.check_pyarmor_available():
|
||||||
|
raise ImportError(
|
||||||
|
"PyArmor 未安装。请使用 'uv add pyarmor' 或 'pip install pyarmor' 安装。"
|
||||||
|
)
|
||||||
|
|
||||||
|
temp_dir = tempfile.mkdtemp(prefix=f"pyarmor_pack_")
|
||||||
|
try:
|
||||||
|
# 创建临时源文件
|
||||||
|
src_filename = f"{module_name.replace('.', '_')}.py"
|
||||||
|
src_file = os.path.join(temp_dir, src_filename)
|
||||||
|
with open(src_file, "w", encoding="utf-8") as f:
|
||||||
|
f.write(source_code)
|
||||||
|
|
||||||
|
# 输出目录
|
||||||
|
output_dir = os.path.join(temp_dir, "dist")
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 调用 pyarmor gen 命令进行混淆
|
||||||
|
cmd = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"pyarmor.cli",
|
||||||
|
"gen",
|
||||||
|
"-O",
|
||||||
|
output_dir,
|
||||||
|
"-i",
|
||||||
|
src_file,
|
||||||
|
]
|
||||||
|
|
||||||
|
# 添加混淆级别参数
|
||||||
|
if self.obf_code == 0:
|
||||||
|
cmd.extend(["--obf-code", "0"])
|
||||||
|
if self.obf_mod == 0:
|
||||||
|
cmd.extend(["--obf-mod", "0"])
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
cwd=temp_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(f"PyArmor 混淆失败:\n{result.stderr}")
|
||||||
|
|
||||||
|
# 读取混淆后的代码
|
||||||
|
obfuscated_file = os.path.join(output_dir, src_filename)
|
||||||
|
if not os.path.exists(obfuscated_file):
|
||||||
|
raise RuntimeError(f"PyArmor 未生成输出文件: {obfuscated_file}")
|
||||||
|
|
||||||
|
with open(obfuscated_file, "r", encoding="utf-8") as f:
|
||||||
|
obfuscated_code = f.read()
|
||||||
|
|
||||||
|
# 查找并读取运行时文件
|
||||||
|
runtime_data = self._find_and_read_runtime(output_dir)
|
||||||
|
|
||||||
|
return obfuscated_code, runtime_data
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if isinstance(e, RuntimeError):
|
||||||
|
raise
|
||||||
|
raise RuntimeError(f"PyArmor 混淆失败 ({module_name}): {e}") from e
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
def _find_and_read_runtime(self, output_dir: str) -> Optional[dict]:
|
||||||
|
"""
|
||||||
|
查找并读取 PyArmor 运行时文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output_dir: PyArmor 输出目录
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
包含运行时文件的字典: {
|
||||||
|
"runtime_name": "pyarmor_runtime_000000",
|
||||||
|
"pyd_data": bytes, # 原生二进制文件
|
||||||
|
"init_data": str, # __init__.py 内容
|
||||||
|
}
|
||||||
|
如果没有找到则返回 None
|
||||||
|
"""
|
||||||
|
output_path = Path(output_dir)
|
||||||
|
# 查找 pyarmor_runtime_* 目录
|
||||||
|
runtime_dirs = list(output_path.glob("pyarmor_runtime_*"))
|
||||||
|
if not runtime_dirs:
|
||||||
|
return None
|
||||||
|
|
||||||
|
runtime_dir = runtime_dirs[0]
|
||||||
|
runtime_name = runtime_dir.name
|
||||||
|
|
||||||
|
result = {"runtime_name": runtime_name, "pyd_data": None, "init_data": None}
|
||||||
|
|
||||||
|
# 查找 .pyd 或 .so 文件
|
||||||
|
runtime_files = list(runtime_dir.glob("pyarmor_runtime.*"))
|
||||||
|
if runtime_files:
|
||||||
|
with open(runtime_files[0], "rb") as f:
|
||||||
|
result["pyd_data"] = f.read()
|
||||||
|
|
||||||
|
# 读取 __init__.py
|
||||||
|
init_file = runtime_dir / "__init__.py"
|
||||||
|
if init_file.exists():
|
||||||
|
with open(init_file, "r", encoding="utf-8") as f:
|
||||||
|
result["init_data"] = f.read()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_runtime_info(obfuscated_code: str) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
从混淆后的代码中提取运行时模块名
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obfuscated_code: 混淆后的源代码
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
运行时模块名,如 'pyarmor_runtime_000000'
|
||||||
|
"""
|
||||||
|
match = re.search(
|
||||||
|
r"from\s+(pyarmor_runtime_\w+)\s+import\s+__pyarmor__",
|
||||||
|
obfuscated_code,
|
||||||
|
)
|
||||||
|
return match.group(1) if match else None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_pyarmor_protected(source_code: str) -> bool:
|
||||||
|
"""
|
||||||
|
检测源代码是否已被 PyArmor 保护
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_code: 要检测的源代码
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否为 PyArmor 保护的代码
|
||||||
|
"""
|
||||||
|
markers = [
|
||||||
|
"pyarmor_runtime",
|
||||||
|
"__pyarmor__",
|
||||||
|
"from pyarmor_runtime",
|
||||||
|
]
|
||||||
|
return any(marker in source_code for marker in markers)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_pyarmor_available() -> bool:
|
||||||
|
"""检查 PyArmor 是否可用"""
|
||||||
|
try:
|
||||||
|
import pyarmor # noqa: F401
|
||||||
|
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
from .sqlite_module_importer import SQLiteModuleImporter
|
from .sqlite_module_importer import SQLiteModuleImporter
|
||||||
from .sqlite_meta_path_finder import SQLiteMetaPathFinder
|
from .sqlite_meta_path_finder import SQLiteMetaPathFinder
|
||||||
|
|
||||||
@@ -8,19 +8,38 @@ from .sqlite_meta_path_finder import SQLiteMetaPathFinder
|
|||||||
class PythonToSQLite:
|
class PythonToSQLite:
|
||||||
"""Python到SQLite的打包工具"""
|
"""Python到SQLite的打包工具"""
|
||||||
|
|
||||||
def __init__(self, db_path: str = "python_modules.db"):
|
def __init__(
|
||||||
|
self,
|
||||||
|
db_path: str = "python_modules.db",
|
||||||
|
key: Optional[str] = None,
|
||||||
|
pyarmor_obfuscate: bool = False,
|
||||||
|
pyarmor_obf_code: int = 1,
|
||||||
|
pyarmor_obf_mod: int = 1,
|
||||||
|
):
|
||||||
self.db_path = db_path
|
self.db_path = db_path
|
||||||
self.importer = SQLiteModuleImporter(db_path)
|
self.key = key
|
||||||
|
self.pyarmor_obfuscate = pyarmor_obfuscate
|
||||||
|
self.pyarmor_obf_code = pyarmor_obf_code
|
||||||
|
self.pyarmor_obf_mod = pyarmor_obf_mod
|
||||||
|
self.importer = SQLiteModuleImporter(
|
||||||
|
db_path,
|
||||||
|
key,
|
||||||
|
pyarmor_obfuscate=pyarmor_obfuscate,
|
||||||
|
pyarmor_obf_code=pyarmor_obf_code,
|
||||||
|
pyarmor_obf_mod=pyarmor_obf_mod,
|
||||||
|
)
|
||||||
|
|
||||||
def pack_directory(
|
def pack_directory(
|
||||||
self,
|
self,
|
||||||
directory_path: str,
|
directory_path: str,
|
||||||
include_patterns: List[str] = None,
|
package_name: str | None = None,
|
||||||
exclude_patterns: List[str] = None,
|
include_patterns: List[str] | None = None,
|
||||||
|
exclude_patterns: List[str] | None = None,
|
||||||
):
|
):
|
||||||
"""打包整个目录 - 修复版"""
|
"""打包整个目录 - 修复版"""
|
||||||
directory = Path(directory_path)
|
directory = Path(directory_path)
|
||||||
package_name = directory.name # 包名
|
if package_name is None:
|
||||||
|
package_name = directory.name # 包名
|
||||||
|
|
||||||
if include_patterns is None:
|
if include_patterns is None:
|
||||||
include_patterns = ["*.py"]
|
include_patterns = ["*.py"]
|
||||||
@@ -66,9 +85,12 @@ class PythonToSQLite:
|
|||||||
is_package=False,
|
is_package=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def pack_module(self, module_path: str, module_name: str = None):
|
def pack_module(self, module_path_str: str, module_name: str | None = None):
|
||||||
"""打包单个模块"""
|
"""打包单个模块"""
|
||||||
module_path = Path(module_path)
|
if module_name is None:
|
||||||
|
raise ValueError("module_name 不能为空")
|
||||||
|
|
||||||
|
module_path = Path(module_path_str)
|
||||||
|
|
||||||
if module_name is None:
|
if module_name is None:
|
||||||
module_name = module_path.stem
|
module_name = module_path.stem
|
||||||
@@ -79,9 +101,10 @@ class PythonToSQLite:
|
|||||||
print(f"打包独立模块: {module_name} (来自: {module_path})")
|
print(f"打包独立模块: {module_name} (来自: {module_path})")
|
||||||
self.importer.add_module(module_name, source_code, is_package=False)
|
self.importer.add_module(module_name, source_code, is_package=False)
|
||||||
|
|
||||||
def install_importer(self):
|
def install_importer(self, key: Optional[str] = None):
|
||||||
"""安装导入器到sys.meta_path"""
|
"""安装导入器到sys.meta_path"""
|
||||||
finder = SQLiteMetaPathFinder(self.db_path)
|
effective_key = key if key is not None else self.key
|
||||||
|
finder = SQLiteMetaPathFinder(self.db_path, effective_key)
|
||||||
# 添加到meta_path开头,优先于文件系统查找
|
# 添加到meta_path开头,优先于文件系统查找
|
||||||
sys.meta_path.insert(0, finder)
|
sys.meta_path.insert(0, finder)
|
||||||
return finder
|
return finder
|
||||||
@@ -89,7 +112,7 @@ class PythonToSQLite:
|
|||||||
def list_modules(self):
|
def list_modules(self):
|
||||||
"""列出数据库中的所有模块"""
|
"""列出数据库中的所有模块"""
|
||||||
cursor = self.importer.connection.execute(
|
cursor = self.importer.connection.execute(
|
||||||
"SELECT module_name, is_package FROM python_modules ORDER BY module_name"
|
"SELECT module_name, is_package FROM python_modules WHERE module_name != 'key' ORDER BY module_name"
|
||||||
)
|
)
|
||||||
return [dict(row) for row in cursor.fetchall()]
|
return [dict(row) for row in cursor.fetchall()]
|
||||||
|
|
||||||
@@ -131,3 +154,21 @@ class PythonToSQLite:
|
|||||||
print(
|
print(
|
||||||
f"警告:模块 {module_name} 的父包 {parent_package} 不是一个包"
|
f"警告:模块 {module_name} 的父包 {parent_package} 不是一个包"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def packer_key(self, key):
|
||||||
|
self.importer.connection.execute(
|
||||||
|
"""
|
||||||
|
INSERT OR REPLACE INTO python_modules
|
||||||
|
(module_name, source_code, bytecode, is_package, metadata, encrypted)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
("key", None, key, None, None, None),
|
||||||
|
)
|
||||||
|
self.importer.connection.commit()
|
||||||
|
|
||||||
|
def get_key(self):
|
||||||
|
cursor = self.importer.connection.execute("""
|
||||||
|
SELECT bytecode FROM python_modules WHERE module_name = 'key'
|
||||||
|
""")
|
||||||
|
row = cursor.fetchone()
|
||||||
|
return row["bytecode"] if row else None
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ from .sqlite_module_loader import SQLiteModuleLoader
|
|||||||
class SQLiteMetaPathFinder(importlib.abc.MetaPathFinder):
|
class SQLiteMetaPathFinder(importlib.abc.MetaPathFinder):
|
||||||
"""SQLite元路径查找器"""
|
"""SQLite元路径查找器"""
|
||||||
|
|
||||||
def __init__(self, db_path: str):
|
def __init__(self, db_path: str, key: str | None = None):
|
||||||
self.db_path = db_path
|
self.db_path = db_path
|
||||||
|
self.key = key
|
||||||
self.connection = sqlite3.connect(db_path)
|
self.connection = sqlite3.connect(db_path)
|
||||||
self.connection.row_factory = sqlite3.Row
|
self.connection.row_factory = sqlite3.Row
|
||||||
# 缓存模块查找结果
|
# 缓存模块查找结果
|
||||||
@@ -31,7 +32,7 @@ class SQLiteMetaPathFinder(importlib.abc.MetaPathFinder):
|
|||||||
# 创建模块规范
|
# 创建模块规范
|
||||||
spec = importlib.util.spec_from_loader(
|
spec = importlib.util.spec_from_loader(
|
||||||
fullname,
|
fullname,
|
||||||
SQLiteModuleLoader(self.connection, fullname, self.db_path),
|
SQLiteModuleLoader(self.connection, fullname, self.db_path, self.key),
|
||||||
origin=f"sqlite://{self.db_path}",
|
origin=f"sqlite://{self.db_path}",
|
||||||
is_package=self.module_cache[fullname],
|
is_package=self.module_cache[fullname],
|
||||||
)
|
)
|
||||||
@@ -44,7 +45,9 @@ class SQLiteMetaPathFinder(importlib.abc.MetaPathFinder):
|
|||||||
if fullname in self.module_cache:
|
if fullname in self.module_cache:
|
||||||
spec = importlib.util.spec_from_loader(
|
spec = importlib.util.spec_from_loader(
|
||||||
fullname,
|
fullname,
|
||||||
SQLiteModuleLoader(self.connection, fullname, self.db_path),
|
SQLiteModuleLoader(
|
||||||
|
self.connection, fullname, self.db_path, self.key
|
||||||
|
),
|
||||||
origin=f"sqlite://{self.db_path}",
|
origin=f"sqlite://{self.db_path}",
|
||||||
is_package=self.module_cache[fullname],
|
is_package=self.module_cache[fullname],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,22 +1,37 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import marshal
|
import marshal
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
from typing import Dict, Optional, Tuple
|
||||||
|
from .encryption import Encryption
|
||||||
|
|
||||||
|
|
||||||
class SQLiteModuleImporter:
|
class SQLiteModuleImporter:
|
||||||
"""从SQLite数据库导入Python模块的导入器"""
|
"""从SQLite数据库导入Python模块的导入器"""
|
||||||
|
|
||||||
def __init__(self, db_path: str):
|
def __init__(
|
||||||
|
self,
|
||||||
|
db_path: str,
|
||||||
|
key: Optional[str] = None,
|
||||||
|
pyarmor_obfuscate: bool = False,
|
||||||
|
pyarmor_obf_code: int = 1,
|
||||||
|
pyarmor_obf_mod: int = 1,
|
||||||
|
):
|
||||||
self.db_path = db_path
|
self.db_path = db_path
|
||||||
self.connection = sqlite3.connect(db_path)
|
self.connection = sqlite3.connect(db_path)
|
||||||
self.connection.row_factory = sqlite3.Row
|
self.connection.row_factory = sqlite3.Row
|
||||||
|
self.encryption = Encryption(key)
|
||||||
|
self.pyarmor_obfuscate = pyarmor_obfuscate
|
||||||
|
self.pyarmor_obf_code = pyarmor_obf_code
|
||||||
|
self.pyarmor_obf_mod = pyarmor_obf_mod
|
||||||
|
self._pyarmor_enc = None
|
||||||
self._create_tables()
|
self._create_tables()
|
||||||
|
|
||||||
def _create_tables(self):
|
def _create_tables(self):
|
||||||
"""创建存储模块的数据库表"""
|
"""创建存储模块的数据库表"""
|
||||||
self.connection.execute(
|
self.connection.execute("""
|
||||||
"""
|
|
||||||
CREATE TABLE IF NOT EXISTS python_modules (
|
CREATE TABLE IF NOT EXISTS python_modules (
|
||||||
module_name TEXT PRIMARY KEY,
|
module_name TEXT PRIMARY KEY,
|
||||||
source_code TEXT,
|
source_code TEXT,
|
||||||
@@ -25,37 +40,151 @@ class SQLiteModuleImporter:
|
|||||||
metadata TEXT,
|
metadata TEXT,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
)
|
)
|
||||||
"""
|
""")
|
||||||
)
|
try:
|
||||||
|
self.connection.execute(
|
||||||
|
"ALTER TABLE python_modules ADD COLUMN encrypted INTEGER DEFAULT 0"
|
||||||
|
)
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 添加 pyarmor_protected 列(用于标记 PyArmor 混淆过的模块)
|
||||||
|
try:
|
||||||
|
self.connection.execute(
|
||||||
|
"ALTER TABLE python_modules ADD COLUMN pyarmor_protected INTEGER DEFAULT 0"
|
||||||
|
)
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 创建 PyArmor 运行时存储表(支持二进制文件和文本文件的存储)
|
||||||
|
self.connection.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS pyarmor_runtime (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
runtime_name TEXT UNIQUE NOT NULL,
|
||||||
|
pyd_data BLOB,
|
||||||
|
init_data TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
|
|
||||||
|
def _get_pyarmor_enc(self):
|
||||||
|
"""惰性获取 PyArmor 加密器"""
|
||||||
|
if self._pyarmor_enc is None:
|
||||||
|
from .pyarmor_encryption import PyArmorEncryption
|
||||||
|
|
||||||
|
self._pyarmor_enc = PyArmorEncryption(
|
||||||
|
key=None,
|
||||||
|
obf_code=self.pyarmor_obf_code,
|
||||||
|
obf_mod=self.pyarmor_obf_mod,
|
||||||
|
)
|
||||||
|
return self._pyarmor_enc
|
||||||
|
|
||||||
def add_module(
|
def add_module(
|
||||||
self,
|
self,
|
||||||
module_name: str,
|
module_name: str,
|
||||||
source_code: str,
|
source_code: str,
|
||||||
is_package: bool = False,
|
is_package: bool = False,
|
||||||
metadata: Dict = None,
|
metadata: Dict | None = None,
|
||||||
):
|
):
|
||||||
"""添加模块到数据库"""
|
"""添加模块到数据库"""
|
||||||
|
pyarmor_protected = 0
|
||||||
|
|
||||||
|
# 如果启用了 PyArmor 混淆,则先混淆源代码
|
||||||
|
if self.pyarmor_obfuscate:
|
||||||
|
pyarmor_enc = self._get_pyarmor_enc()
|
||||||
|
if pyarmor_enc.check_pyarmor_available():
|
||||||
|
obfuscated_code, runtime_dict = pyarmor_enc.obfuscate_source(
|
||||||
|
source_code, module_name
|
||||||
|
)
|
||||||
|
source_code = obfuscated_code
|
||||||
|
pyarmor_protected = 1
|
||||||
|
|
||||||
|
# 存储运行时文件
|
||||||
|
runtime_name = None
|
||||||
|
if runtime_dict is not None:
|
||||||
|
runtime_name = runtime_dict.get("runtime_name")
|
||||||
|
self._store_runtime(runtime_dict)
|
||||||
|
|
||||||
|
# 将运行时名存入 metadata,以便在删除源代码后仍能定位运行时
|
||||||
|
if runtime_name and metadata is None:
|
||||||
|
metadata = {"pyarmor_runtime": runtime_name}
|
||||||
|
elif runtime_name and isinstance(metadata, dict):
|
||||||
|
metadata["pyarmor_runtime"] = runtime_name
|
||||||
|
else:
|
||||||
|
print(f"警告: PyArmor 未安装,跳过模块 '{module_name}' 的 PyArmor 混淆")
|
||||||
|
|
||||||
# 编译为字节码
|
# 编译为字节码
|
||||||
bytecode = compile(source_code, f"<sqlite_module:{module_name}>", "exec")
|
bytecode = compile(source_code, f"<sqlite_module:{module_name}>", "exec")
|
||||||
bytecode_blob = marshal.dumps(bytecode)
|
bytecode_blob = marshal.dumps(bytecode)
|
||||||
|
|
||||||
metadata_str = str(metadata) if metadata else "{}"
|
metadata_str = str(metadata) if metadata else "{}"
|
||||||
|
encrypted_flag = 0
|
||||||
|
|
||||||
|
# 如果加密器已设置,则加密字节码
|
||||||
|
if self.encryption.cipher is not None:
|
||||||
|
bytecode_blob = self.encryption.encrypt(bytecode_blob)
|
||||||
|
encrypted_flag = 1
|
||||||
|
|
||||||
self.connection.execute(
|
self.connection.execute(
|
||||||
"""
|
"""
|
||||||
INSERT OR REPLACE INTO python_modules
|
INSERT OR REPLACE INTO python_modules
|
||||||
(module_name, source_code, bytecode, is_package, metadata)
|
(module_name, source_code, bytecode, is_package, metadata, encrypted, pyarmor_protected)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(module_name, source_code, bytecode_blob, is_package, metadata_str),
|
(
|
||||||
|
module_name,
|
||||||
|
source_code,
|
||||||
|
bytecode_blob,
|
||||||
|
is_package,
|
||||||
|
metadata_str,
|
||||||
|
encrypted_flag,
|
||||||
|
pyarmor_protected,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
|
|
||||||
def add_module_from_file(self, file_path: str, module_name: str = None):
|
def _store_runtime(self, runtime_dict: dict):
|
||||||
|
"""存储 PyArmor 运行时文件到数据库"""
|
||||||
|
self.connection.execute(
|
||||||
|
"""
|
||||||
|
INSERT OR REPLACE INTO pyarmor_runtime
|
||||||
|
(runtime_name, pyd_data, init_data)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
runtime_dict.get("runtime_name"),
|
||||||
|
runtime_dict.get("pyd_data"),
|
||||||
|
runtime_dict.get("init_data"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.connection.commit()
|
||||||
|
|
||||||
|
def get_runtime(self, runtime_name: str) -> Optional[dict]:
|
||||||
|
"""从数据库获取 PyArmor 运行时文件数据"""
|
||||||
|
cursor = self.connection.execute(
|
||||||
|
"SELECT runtime_name, pyd_data, init_data FROM pyarmor_runtime WHERE runtime_name = ?",
|
||||||
|
(runtime_name,),
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
return {
|
||||||
|
"runtime_name": row["runtime_name"],
|
||||||
|
"pyd_data": row["pyd_data"],
|
||||||
|
"init_data": row["init_data"],
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
def list_runtimes(self):
|
||||||
|
"""列出数据库中的所有 PyArmor 运行时"""
|
||||||
|
cursor = self.connection.execute(
|
||||||
|
"SELECT runtime_name, created_at FROM pyarmor_runtime ORDER BY id"
|
||||||
|
)
|
||||||
|
return [dict(row) for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
def add_module_from_file(self, file_path_str: str, module_name: str | None = None):
|
||||||
"""从文件添加模块"""
|
"""从文件添加模块"""
|
||||||
file_path = Path(file_path)
|
file_path = Path(file_path_str)
|
||||||
|
|
||||||
if module_name is None:
|
if module_name is None:
|
||||||
# 从文件名推导模块名
|
# 从文件名推导模块名
|
||||||
@@ -66,9 +195,9 @@ class SQLiteModuleImporter:
|
|||||||
|
|
||||||
self.add_module(module_name, source_code)
|
self.add_module(module_name, source_code)
|
||||||
|
|
||||||
def add_package(self, package_path: str, package_name: str = None):
|
def add_package(self, package_path_str: str, package_name: str | None = None):
|
||||||
"""添加整个包到数据库"""
|
"""添加整个包到数据库"""
|
||||||
package_path = Path(package_path)
|
package_path = Path(package_path_str)
|
||||||
|
|
||||||
if package_name is None:
|
if package_name is None:
|
||||||
package_name = package_path.name
|
package_name = package_path.name
|
||||||
|
|||||||
@@ -3,18 +3,31 @@ import importlib
|
|||||||
import importlib.abc
|
import importlib.abc
|
||||||
import marshal
|
import marshal
|
||||||
import types
|
import types
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from .encryption import Encryption
|
||||||
|
|
||||||
|
|
||||||
class SQLiteModuleLoader(importlib.abc.Loader):
|
class SQLiteModuleLoader(importlib.abc.Loader):
|
||||||
"""SQLite模块加载器"""
|
"""SQLite模块加载器"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, db_connection: sqlite3.Connection, module_name: str, db_path: str = None
|
self,
|
||||||
|
db_connection: sqlite3.Connection,
|
||||||
|
module_name: str,
|
||||||
|
db_path: str | None = None,
|
||||||
|
key: str | None = None,
|
||||||
):
|
):
|
||||||
self.db_connection = db_connection
|
self.db_connection = db_connection
|
||||||
self.module_name = module_name
|
self.module_name = module_name
|
||||||
self.db_path = db_path
|
self.db_path = db_path
|
||||||
self.db_connection.row_factory = sqlite3.Row
|
self.db_connection.row_factory = sqlite3.Row
|
||||||
|
self.encryption = Encryption(key)
|
||||||
|
self._runtime_temp_dir = None
|
||||||
|
|
||||||
def create_module(self, spec):
|
def create_module(self, spec):
|
||||||
"""创建模块对象"""
|
"""创建模块对象"""
|
||||||
@@ -35,10 +48,10 @@ class SQLiteModuleLoader(importlib.abc.Loader):
|
|||||||
# 创建普通模块
|
# 创建普通模块
|
||||||
return None # 使用默认创建方式
|
return None # 使用默认创建方式
|
||||||
|
|
||||||
def exec_module(self, module):
|
def exec_module(self, module): # type: ignore
|
||||||
"""执行模块代码"""
|
"""执行模块代码"""
|
||||||
cursor = self.db_connection.execute(
|
cursor = self.db_connection.execute(
|
||||||
"SELECT bytecode, source_code, is_package FROM python_modules WHERE module_name = ?",
|
"SELECT bytecode, is_package, encrypted, pyarmor_protected, source_code, metadata FROM python_modules WHERE module_name = ?",
|
||||||
(self.module_name,),
|
(self.module_name,),
|
||||||
)
|
)
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
@@ -46,8 +59,27 @@ class SQLiteModuleLoader(importlib.abc.Loader):
|
|||||||
if not row:
|
if not row:
|
||||||
raise ImportError(f"No module named '{self.module_name}' in database")
|
raise ImportError(f"No module named '{self.module_name}' in database")
|
||||||
|
|
||||||
|
pyarmor_protected = row["pyarmor_protected"]
|
||||||
|
|
||||||
|
# 如果是 PyArmor 保护的模块,需要先部署运行时再执行
|
||||||
|
if pyarmor_protected:
|
||||||
|
return self._exec_pyarmor_module(module, row)
|
||||||
|
|
||||||
# 从字节码加载
|
# 从字节码加载
|
||||||
bytecode = marshal.loads(row["bytecode"])
|
bytecode_blob = row["bytecode"]
|
||||||
|
encrypted = row["encrypted"]
|
||||||
|
|
||||||
|
# 如果字节码是加密的,需要解密
|
||||||
|
if encrypted:
|
||||||
|
if self.encryption.cipher is None:
|
||||||
|
raise ImportError(
|
||||||
|
f"Module '{self.module_name}' has encrypted bytecode but no decryption key provided. "
|
||||||
|
"Please set the key when installing the importer."
|
||||||
|
)
|
||||||
|
bytecode_blob = self.encryption.decrypt(bytecode_blob)
|
||||||
|
|
||||||
|
# 从字节码加载
|
||||||
|
bytecode = marshal.loads(bytecode_blob)
|
||||||
|
|
||||||
# 在模块的命名空间中执行字节码
|
# 在模块的命名空间中执行字节码
|
||||||
exec(bytecode, module.__dict__)
|
exec(bytecode, module.__dict__)
|
||||||
@@ -61,3 +93,157 @@ class SQLiteModuleLoader(importlib.abc.Loader):
|
|||||||
module.__file__ = f"sqlite://{self.db_path}/{self.module_name}"
|
module.__file__ = f"sqlite://{self.db_path}/{self.module_name}"
|
||||||
|
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
def _deploy_runtime(self, runtime_name: str) -> str | None:
|
||||||
|
"""
|
||||||
|
部署 PyArmor 运行时到临时目录
|
||||||
|
|
||||||
|
Args:
|
||||||
|
runtime_name: 运行时模块名,如 'pyarmor_runtime_000000'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
临时目录路径,如果部署失败则返回 None
|
||||||
|
"""
|
||||||
|
if self.db_path is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .sqlite_module_importer import SQLiteModuleImporter
|
||||||
|
|
||||||
|
importer = SQLiteModuleImporter(self.db_path)
|
||||||
|
runtime_dict = importer.get_runtime(runtime_name)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not runtime_dict:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 创建临时目录来存放运行时包
|
||||||
|
temp_dir = tempfile.mkdtemp(prefix="pyarmor_runtime_")
|
||||||
|
runtime_pkg_dir = os.path.join(temp_dir, runtime_name)
|
||||||
|
os.makedirs(runtime_pkg_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 确定运行时二进制文件名(.pyd for Windows, .so for Linux/Mac)
|
||||||
|
pyd_data = runtime_dict.get("pyd_data")
|
||||||
|
if pyd_data:
|
||||||
|
if sys.platform == "win32":
|
||||||
|
so_filename = "pyarmor_runtime.pyd"
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
so_filename = "pyarmor_runtime.dylib"
|
||||||
|
else:
|
||||||
|
so_filename = "pyarmor_runtime.so"
|
||||||
|
|
||||||
|
so_path = os.path.join(runtime_pkg_dir, so_filename)
|
||||||
|
with open(so_path, "wb") as f:
|
||||||
|
f.write(pyd_data)
|
||||||
|
|
||||||
|
# 写入 __init__.py
|
||||||
|
init_data = runtime_dict.get("init_data")
|
||||||
|
if init_data:
|
||||||
|
init_path = os.path.join(runtime_pkg_dir, "__init__.py")
|
||||||
|
with open(init_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(init_data)
|
||||||
|
|
||||||
|
# 将临时目录添加到 sys.path,使运行时包可被导入
|
||||||
|
if temp_dir not in sys.path:
|
||||||
|
sys.path.insert(0, temp_dir)
|
||||||
|
|
||||||
|
return temp_dir
|
||||||
|
|
||||||
|
def _extract_runtime_name_from_source(self, source_code: str) -> str | None:
|
||||||
|
"""从混淆代码中提取运行时模块名"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
match = re.search(
|
||||||
|
r"from\s+(pyarmor_runtime_\w+)\s+import\s+__pyarmor__",
|
||||||
|
source_code,
|
||||||
|
)
|
||||||
|
return match.group(1) if match else None
|
||||||
|
|
||||||
|
def _extract_runtime_name_from_metadata(
|
||||||
|
self, metadata_str: str | None
|
||||||
|
) -> str | None:
|
||||||
|
"""从 metadata JSON 中提取运行时模块名"""
|
||||||
|
if not metadata_str:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
metadata = json.loads(metadata_str)
|
||||||
|
return metadata.get("pyarmor_runtime")
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _exec_pyarmor_module(self, module, row):
|
||||||
|
"""
|
||||||
|
执行 PyArmor 保护的模块
|
||||||
|
|
||||||
|
PyArmor 混淆后的代码需要在运行时导入 pyarmor_runtime_xxx 包。
|
||||||
|
该包包含 __init__.py 和原生 .pyd/.so 文件。
|
||||||
|
|
||||||
|
支持两种模式:
|
||||||
|
1. 有源代码:从源代码编译执行(标准模式)
|
||||||
|
2. 无源代码:从已编译的字节码执行(delete_source 后的模式)
|
||||||
|
"""
|
||||||
|
source_code = row["source_code"]
|
||||||
|
bytecode_blob = row["bytecode"]
|
||||||
|
encrypted = row["encrypted"]
|
||||||
|
metadata_str = row["metadata"]
|
||||||
|
|
||||||
|
# 确定运行时名:优先从源代码提取,其次从 metadata
|
||||||
|
runtime_name = None
|
||||||
|
if source_code:
|
||||||
|
runtime_name = self._extract_runtime_name_from_source(source_code)
|
||||||
|
if not runtime_name:
|
||||||
|
runtime_name = self._extract_runtime_name_from_metadata(metadata_str)
|
||||||
|
|
||||||
|
# 部署运行时
|
||||||
|
if runtime_name:
|
||||||
|
self._runtime_temp_dir = self._deploy_runtime(runtime_name)
|
||||||
|
if self._runtime_temp_dir:
|
||||||
|
print(f" 已部署 PyArmor 运行时: {runtime_name}")
|
||||||
|
|
||||||
|
# 设置 __file__ 属性(PyArmor 的 __pyarmor__() 需要它)
|
||||||
|
module_file = f"sqlite://{self.db_path}/{self.module_name}"
|
||||||
|
module.__file__ = module_file
|
||||||
|
|
||||||
|
# 如果是包,设置__path__和__package__属性
|
||||||
|
if row["is_package"]:
|
||||||
|
module.__path__ = []
|
||||||
|
module.__package__ = self.module_name
|
||||||
|
|
||||||
|
# 执行代码
|
||||||
|
if source_code:
|
||||||
|
# 模式1:有源代码 - 编译后执行
|
||||||
|
bytecode = compile(
|
||||||
|
source_code, f"<sqlite_module:{self.module_name}>", "exec"
|
||||||
|
)
|
||||||
|
exec(bytecode, module.__dict__)
|
||||||
|
else:
|
||||||
|
# 模式2:无源代码 - 从已存储的字节码执行
|
||||||
|
if not bytecode_blob:
|
||||||
|
raise ImportError(
|
||||||
|
f"PyArmor protected module '{self.module_name}' has no source code or bytecode available"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 如果字节码是加密的,需要解密
|
||||||
|
if encrypted:
|
||||||
|
if self.encryption.cipher is None:
|
||||||
|
raise ImportError(
|
||||||
|
f"Module '{self.module_name}' has encrypted bytecode but no decryption key provided."
|
||||||
|
)
|
||||||
|
bytecode_blob = self.encryption.decrypt(bytecode_blob)
|
||||||
|
|
||||||
|
bytecode = marshal.loads(bytecode_blob)
|
||||||
|
exec(bytecode, module.__dict__)
|
||||||
|
|
||||||
|
return module
|
||||||
|
|
||||||
|
def cleanup_runtime(self):
|
||||||
|
"""清理 PyArmor 运行时临时文件"""
|
||||||
|
if self._runtime_temp_dir and os.path.exists(self._runtime_temp_dir):
|
||||||
|
try:
|
||||||
|
if self._runtime_temp_dir in sys.path:
|
||||||
|
sys.path.remove(self._runtime_temp_dir)
|
||||||
|
shutil.rmtree(self._runtime_temp_dir, ignore_errors=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._runtime_temp_dir = None
|
||||||
|
|||||||
Reference in New Issue
Block a user