modify: pyarmor 加密支持

This commit is contained in:
2026-05-04 19:06:51 +08:00
parent 0388984080
commit 1fdb5bd1bf
17 changed files with 1418 additions and 90 deletions
+3
View File
@@ -12,3 +12,6 @@ wheels/
output.md
log/
*.mb
*.mbank
dual_protection_
*.db
+203 -70
View File
@@ -1,15 +1,16 @@
# Python Module Bank - SQLite 模块打包系统
一个将Python模块打包到SQLite数据库,并支持从数据库直接导入模块的工具系统。
一个将 Python 模块打包到 SQLite 数据库,并支持从数据库直接导入模块的工具系统。支持 **AES 字节码加密** 和 **PyArmor 代码混淆** 双重保护机制。
## 🌟 特性
- **单文件分发** - 将所有模块打包到单个SQLite数据库文件
- **源代码保护** - 模块以编译后的字节码形式存储
- **动态导入** - 运行时直接从数据库加载模块,无需文件系统
- **单文件分发** - 将所有模块打包到单个 SQLite 数据库文件
- **AES 字节码加密** - 编译后的字节码使用 Fernet (AES-128-CBC) 加密存储
- **PyArmor 代码混淆** - 支持使用 PyArmor 对 Python 源代码进行混淆保护
- **双层保护** - 可同时使用 PyArmor 混淆 + AES 字节码加密,提供双重安全保障
- **动态导入** - 运行时直接从数据库加载模块,无缝集成 Python 导入系统
- **完整包支持** - 支持包结构和子模块导入
- **CLI工具** - 提供完整的命令行接口
- **导入钩子** - 无缝集成Python导入系统
- **CLI 工具** - 提供完整的命令行接口
## 📦 安装
@@ -21,69 +22,154 @@ cd module-bank
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. 创建示例模块
```python
# my_package/my_module.py
# my_package/__init__.py
def hello():
print("Hello from my_module!")
print("Hello from my_package!")
return "success"
VERSION = "1.0.0"
```
```python
# my_package/utils.py
def add(a, b):
return a + b
```
### 2. 打包模块到数据库
```python
# pack_example.py
from module_bank import PythonToSQLite
from module_bank.encryption import Encryption
#### 使用 AES 加密(推荐)
key = Encryption.generate_key()
```bash
# 生成密钥并打包
mbank pack my_package --db my_package.mbank --key "$(python -c "from module_bank.encryption import Encryption; print(Encryption.generate_key())")"
```
with open("my_package.key", "w") as key_file:
key_file.write(key)
#### 使用 PyArmor 混淆
packer = PythonToSQLite("my_package.mbank", key)
packer.pack_directory("my_package")
```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. 从数据库导入
```python
from module_bank import PythonToSQLite
with open("my_package.key") as f:
key = f.read()
# import_example.py
from module_bank.python_to_sqlite import PythonToSQLite
packer = PythonToSQLite("my_package.mbank")
finder = packer.install_importer(key)
finder = packer.install_importer(key="your-encryption-key") # 如果加密了需要提供密钥
from my_package import my_module
from my_package import utils
from my_package import hello
print(my_module)
my_module.hello()
print(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
# 打包模块或目录
mbank pack my_package --db modules.db --key my_package_key
```bash
# 打包单个模块
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"
# 列出数据库中的模块
mbank list --db modules.db
# 删除源代码
mbank delete_source --db modules.db
mbank delete_source --db modules.db --module my_module
# 安装导入器并进入交互模式
mbank install --db modules.db --key my_package_key
mbank install --db modules.db
mbank install --db modules.db --key "your-key"
```
### 编程接口
@@ -92,8 +178,21 @@ mbank install --db modules.db --key my_package_key
```python
from module_bank import PythonToSQLite
from module_bank.encryption import Encryption
packer = PythonToSQLite("modules.mbank")
# 生成加密密钥
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")
@@ -109,21 +208,25 @@ packer.verify_package_structure()
```python
from module_bank import PythonToSQLite
import sys
# 如果数据库中的模块被 AES 加密了,需要提供密钥
packer = PythonToSQLite("modules.mbank")
# 安装导入器到sys.meta_path
finder = packer.install_importer()
# 安装导入器到 sys.meta_path
finder = packer.install_importer(key="your-key")
# 列出所有可用模块
modules = packer.list_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.submodule
print(my_package)
my_package.hello()
```
## 🏗️ 架构设计
@@ -132,37 +235,49 @@ import my_package.submodule
```python
src/module_bank/
├── python_to_sqlite.py # 主打包类
├── sqlite_module_importer.py # 数据库存储管理器
├── sqlite_meta_path_finder.py # 元路径查找器
├── sqlite_module_loader.py # 模块加载器
├── encryption.py # 密钥管理
├── cli.py # 命令行接口
└── __init__.py # 模块导出
├── python_to_sqlite.py # 主打包类
├── sqlite_module_importer.py # 数据库存储管理器
├── sqlite_meta_path_finder.py # 元路径查找器
├── sqlite_module_loader.py # 模块加载器
├── encryption.py # AES 密钥管理
├── pyarmor_encryption.py # PyArmor 混淆封装
├── cli.py # 命令行接口
└── __init__.py # 模块导出
```
### 数据流
```text
1. 打包阶段:
.py文件 → 编译为字节码 → 存储到SQLite数据库
.py 文件 → (可选: PyArmor 混淆) → 编译为字节码 → (可选: AES 加密) → 存储到 SQLite
2. 导入阶段:
导入请求 → MetaPathFinder查找 → ModuleLoader加载 → 执行模块
导入请求 → MetaPathFinder 查找 → ModuleLoader 加载 → (AES 解密) → (PyArmor 运行时部署) → 执行模块
```
### 数据库模式
```sql
-- 模块表
CREATE TABLE python_modules (
module_name TEXT PRIMARY KEY,
source_code TEXT, -- 源代码(可选)
bytecode BLOB, -- 编译后的字节码
is_package BOOLEAN, -- 是否是包
metadata TEXT, -- 元数据(JSON格式)
encrypted INTEGER DEFAULT 0, -- 是否已加密
source_code TEXT, -- 源代码(PyArmor 混淆后)
bytecode BLOB, -- 编译后的字节码(可选 AES 加密)
is_package BOOLEAN, -- 是否是包
metadata TEXT, -- 元数据(JSON 格式)
encrypted INTEGER DEFAULT 0, -- AES 是否已加密
pyarmor_protected INTEGER DEFAULT 0, -- 是否已 PyArmor 混淆
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
);
```
## 🔧 高级功能
@@ -173,7 +288,7 @@ CREATE TABLE python_modules (
# 打包时排除特定文件
packer.pack_directory(
"my_project/",
exclude_patterns=["*_test.py", "*.pyc", "__pycache__"]
exclude_patterns=["*_test.py", "*.pyc", "__pycache__"],
)
```
@@ -185,42 +300,56 @@ packer.importer.add_module(
"my_module",
source_code,
is_package=False,
metadata={"version": "1.0", "author": "me"}
metadata={"version": "1.0", "author": "me"},
)
```
### 混合导入
```python
# 可以同时使用文件系统和数据库导入
# 数据库导入器优先级更高
# 数据库导入器默认插入到 meta_path 开头,优先级高于文件系统
import sys
from module_bank import PythonToSQLite
packer = PythonToSQLite("modules.db")
finder = packer.install_importer() # 插入到meta_path开头
finder = packer.install_importer()
# 如果需要文件系统优先,可以调整插入位置
# 如果需要文件系统优先,将查找器追加到末尾
sys.meta_path.remove(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版本兼容
- 不支持C扩展模块
- 不支持需要文件系统资源的模块(如__file__依赖)
- 字节码不跨 Python 版本兼容(3.12 编译的模块无法在 3.11 加载)
- 不支持 C 扩展模块
- PyArmor 运行时文件与操作系统相关(Windows 用 `.pyd`,Linux 用 `.so`)
- 运行时文件需在目标系统上重新打包
### 性能
- **启动时**:有一次性数据库查询和反序列化开销
- **运行时**:与传统导入性能相同(使用sys.modules缓存)
- **启动时**:有一次性数据库查询和反序列化/运行时部署开销
- **运行时**:与传统导入性能相同(使用 sys.modules 缓存)
- **最佳适用**:长期运行的服务、桌面应用
### 更新模块
@@ -249,11 +378,15 @@ cp modules.backup.mbank modules.mbank
## 📚 应用场景
1. 商业软件分发 - 保护源代码知识产权
2. 插件系统 - 动态加载数据库中的插件模块
3. 教育平台 - 安全分发练习代码
4. 微服务 - 打包多个服务模块到单个文件
5. 嵌入式系统 - 减少文件系统依赖
1. **商业软件分发** - PyArmor 混淆保护源代码知识产权
2. **安全敏感环境** - AES 加密确保即使数据库泄露也无法还原代码
3. **插件系统** - 动态加载数据库中的加密/混淆插件模块
4. **教育平台** - 安全分发练习代码,防止学生抄袭
5. **微服务** - 打包多个服务模块到单个加密文件
6. **嵌入式系统** - 减少文件系统依赖,提高安全性
---
**注意**: 本工具主要用于模块分发和部署场景,不适合开发阶段的频繁修改。
**许可证**: MIT License
+202
View File
@@ -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()
+57
View File
@@ -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 运行时 → 无法执行混淆代码
- 两者同时具备 → 才能解密和执行
""")
+2 -1
View File
@@ -1,12 +1,13 @@
[project]
dependencies = [
"cryptography>=45.0.7",
"pyarmor>=9.2.4",
]
description = "Add your description here"
name = "module_bank"
readme = "README.md"
requires-python = ">=3.7"
version = "0.2.3"
version = "0.3.0"
[[tool.uv.index]]
default = true
+295
View File
@@ -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__
File diff suppressed because one or more lines are too long
+53
View File
@@ -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
View File
@@ -2,10 +2,12 @@ from .python_to_sqlite import PythonToSQLite
from .sqlite_module_importer import SQLiteModuleImporter
from .sqlite_module_loader import SQLiteModuleLoader
from .sqlite_meta_path_finder import SQLiteMetaPathFinder
from .pyarmor_encryption import PyArmorEncryption
__all__ = [
"PythonToSQLite",
"SQLiteModuleImporter",
"SQLiteModuleLoader",
"SQLiteMetaPathFinder",
"PyArmorEncryption",
]
+37 -1
View File
@@ -14,6 +14,25 @@ def main():
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="列出数据库中的模块")
@@ -40,7 +59,13 @@ def main():
if args.command == "pack":
from .python_to_sqlite import PythonToSQLite
packer = PythonToSQLite(args.db, key=args.key)
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():
packer.pack_directory(args.source)
@@ -49,6 +74,17 @@ def main():
packer.pack_module(args.source, args.name)
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":
from .python_to_sqlite import PythonToSQLite
+202
View File
@@ -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
+22 -5
View File
@@ -8,10 +8,26 @@ from .sqlite_meta_path_finder import SQLiteMetaPathFinder
class PythonToSQLite:
"""Python到SQLite的打包工具"""
def __init__(self, db_path: str = "python_modules.db", key: Optional[str] = None):
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.key = key
self.importer = SQLiteModuleImporter(db_path, 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(
self,
@@ -146,12 +162,13 @@ class PythonToSQLite:
(module_name, source_code, bytecode, is_package, metadata, encrypted)
VALUES (?, ?, ?, ?, ?, ?)
""",
("key", key, None, None, None, None),
("key", None, key, None, None, None),
)
self.importer.connection.commit()
def get_key(self):
cursor = self.importer.connection.execute("""
SELECT source_code FROM python_modules WHERE module_name = 'key'
SELECT bytecode FROM python_modules WHERE module_name = 'key'
""")
return cursor.fetchone()
row = cursor.fetchone()
return row["bytecode"] if row else None
+107 -9
View File
@@ -1,25 +1,37 @@
import sqlite3
import marshal
import base64
import os
import json
from pathlib import Path
from typing import Dict, Optional
from typing import Dict, Optional, Tuple
from .encryption import Encryption
class SQLiteModuleImporter:
"""从SQLite数据库导入Python模块的导入器"""
def __init__(self, db_path: str, key: Optional[str] = None):
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.connection = sqlite3.connect(db_path)
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()
def _create_tables(self):
"""创建存储模块的数据库表"""
self.connection.execute(
"""
self.connection.execute("""
CREATE TABLE IF NOT EXISTS python_modules (
module_name TEXT PRIMARY KEY,
source_code TEXT,
@@ -28,17 +40,46 @@ class SQLiteModuleImporter:
metadata TEXT,
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()
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(
self,
module_name: str,
@@ -47,6 +88,24 @@ class SQLiteModuleImporter:
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
# 存储运行时文件
if runtime_dict is not None:
self._store_runtime(runtime_dict)
else:
print(f"警告: PyArmor 未安装,跳过模块 '{module_name}' 的 PyArmor 混淆")
# 编译为字节码
bytecode = compile(source_code, f"<sqlite_module:{module_name}>", "exec")
bytecode_blob = marshal.dumps(bytecode)
@@ -62,8 +121,8 @@ class SQLiteModuleImporter:
self.connection.execute(
"""
INSERT OR REPLACE INTO python_modules
(module_name, source_code, bytecode, is_package, metadata, encrypted)
VALUES (?, ?, ?, ?, ?, ?)
(module_name, source_code, bytecode, is_package, metadata, encrypted, pyarmor_protected)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
module_name,
@@ -72,10 +131,49 @@ class SQLiteModuleImporter:
is_package,
metadata_str,
encrypted_flag,
pyarmor_protected,
),
)
self.connection.commit()
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_str)
+113 -1
View File
@@ -3,6 +3,11 @@ import importlib
import importlib.abc
import marshal
import types
import os
import sys
import tempfile
import shutil
from pathlib import Path
from .encryption import Encryption
@@ -21,6 +26,7 @@ class SQLiteModuleLoader(importlib.abc.Loader):
self.db_path = db_path
self.db_connection.row_factory = sqlite3.Row
self.encryption = Encryption(key)
self._runtime_temp_dir = None
def create_module(self, spec):
"""创建模块对象"""
@@ -44,7 +50,7 @@ class SQLiteModuleLoader(importlib.abc.Loader):
def exec_module(self, module): # type: ignore
"""执行模块代码"""
cursor = self.db_connection.execute(
"SELECT bytecode, is_package, encrypted FROM python_modules WHERE module_name = ?",
"SELECT bytecode, is_package, encrypted, pyarmor_protected, source_code FROM python_modules WHERE module_name = ?",
(self.module_name,),
)
row = cursor.fetchone()
@@ -52,6 +58,12 @@ class SQLiteModuleLoader(importlib.abc.Loader):
if not row:
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_blob = row["bytecode"]
encrypted = row["encrypted"]
@@ -80,3 +92,103 @@ class SQLiteModuleLoader(importlib.abc.Loader):
module.__file__ = f"sqlite://{self.db_path}/{self.module_name}"
return module
def _exec_pyarmor_module(self, module, row):
"""
执行 PyArmor 保护的模块
PyArmor 混淆后的代码需要在运行时导入 pyarmor_runtime_xxx 包。
该包包含 __init__.py 和原生 .pyd/.so 文件。
我们需要从数据库提取这些文件并重建包结构到临时目录。
"""
source_code = row["source_code"]
if not source_code:
raise ImportError(
f"PyArmor protected module '{self.module_name}' has no source code available"
)
# 提取运行时模块名
import re
match = re.search(
r"from\s+(pyarmor_runtime_\w+)\s+import\s+__pyarmor__",
source_code,
)
if not match:
# 没有找到运行时引用,直接执行源代码
bytecode = compile(
source_code, f"<sqlite_module:{self.module_name}>", "exec"
)
exec(bytecode, module.__dict__)
module.__file__ = f"sqlite://{self.db_path}/{self.module_name}"
return module
runtime_name = match.group(1)
# 从数据库获取运行时包数据
runtime_dict = None
if self.db_path is not None:
try:
from .sqlite_module_importer import SQLiteModuleImporter
importer = SQLiteModuleImporter(self.db_path)
runtime_dict = importer.get_runtime(runtime_name)
except Exception:
pass
if runtime_dict:
# 创建临时目录来存放运行时包
self._runtime_temp_dir = tempfile.mkdtemp(prefix="pyarmor_runtime_")
runtime_pkg_dir = os.path.join(self._runtime_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 = f"pyarmor_runtime.pyd"
elif sys.platform == "darwin":
so_filename = f"pyarmor_runtime.dylib"
else:
so_filename = f"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 self._runtime_temp_dir not in sys.path:
sys.path.insert(0, self._runtime_temp_dir)
# 设置 __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
# 执行混淆后的代码
bytecode = compile(source_code, f"<sqlite_module:{self.module_name}>", "exec")
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
Generated
+114 -2
View File
@@ -891,11 +891,12 @@ wheels = [
[[package]]
name = "module-bank"
version = "0.2.2"
version = "0.3.0"
source = { editable = "." }
dependencies = [
{ name = "cryptography", version = "45.0.7", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.8'" },
{ name = "cryptography", version = "46.0.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.8'" },
{ name = "pyarmor" },
]
[package.dev-dependencies]
@@ -906,7 +907,10 @@ dev = [
]
[package.metadata]
requires-dist = [{ name = "cryptography", specifier = ">=45.0.7" }]
requires-dist = [
{ name = "cryptography", specifier = ">=45.0.7" },
{ name = "pyarmor", specifier = ">=9.2.4" },
]
[package.metadata.requires-dev]
dev = [{ name = "twine", specifier = ">=4.0.2" }]
@@ -999,6 +1003,114 @@ wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/09/054aea9b7534a15ad38a363a2bd974c20646ab1582a387a95b8df1bfea1c/pkginfo-1.10.0-py3-none-any.whl", hash = "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097", size = 30392, upload-time = "2024-03-03T08:34:18.891Z" },
]
[[package]]
name = "pyarmor"
version = "9.2.4"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
dependencies = [
{ name = "pyarmor-cli-core" },
]
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/58/6f51b2a14fe14e9a98807cee85055ffba91b3991420a884f606bbdae6b76/pyarmor-9.2.4.tar.gz", hash = "sha256:09e9b6dddb65d287c07aa428933d8760821fa05614a25c30b5595222e777d60d", size = 3012328, upload-time = "2026-03-18T02:01:38.759Z" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/2f/d9/9d324a9acbe2daa37893895beeac201503d3f822196107185da16f21ed1b/pyarmor-9.2.4-py3-none-any.whl", hash = "sha256:2d7acea204739a5b390dcb9010b18ac747d646d1eac31d5f03d7b5670be11f8f", size = 3071639, upload-time = "2026-03-18T02:01:45.165Z" },
]
[[package]]
name = "pyarmor-cli-core"
version = "8.1.1"
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
wheels = [
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/26/3fd196462de9c66551cc03a5def656c1a27a64148421cc5528de3429b48f/pyarmor_cli_core-8.1.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:a5dc6923cc379a797d3265161cddc4db713577bdb3cfbb0f3663812cd945e4b6", size = 510402, upload-time = "2026-04-09T01:26:07.223Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/0d/2961d4ed2c8d582b1e078bda1e80689a5394955e06da915c26d222c56399/pyarmor_cli_core-8.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fe0c6782b4b0b26aa1bdc68e8f4cf772198c2ea548d4568d2334fcc30fa7f34b", size = 402418, upload-time = "2026-04-09T01:26:12.769Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/a8/8c415f417299d7490151096ee78be4d97eef956e66a771b19a5161a93bb1/pyarmor_cli_core-8.1.1-cp310-cp310-manylinux1_i686.whl", hash = "sha256:943d584627fb48975b567f442a3ac1b71473582eec17606dd18240921d52b263", size = 481568, upload-time = "2026-04-09T01:26:17.196Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/ba/fd5acb8f325304ac918f14b765988622dbf526d5eed5c17b1f5b1f1fa64f/pyarmor_cli_core-8.1.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:f2a1777205bcc3737594e9fa111552ccd7877efc29ab2e2b13e4769f7eba3aff", size = 452540, upload-time = "2026-04-09T01:26:21.782Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/92/cbc1184315524e6bb4226c962c3fc11326c500d1f333a734a9fbea8f724f/pyarmor_cli_core-8.1.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d8f6d9d916cb83a1ac3256576158528d5bfb02404f324713ffe942d743a759d6", size = 598833, upload-time = "2026-04-09T01:26:26.116Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/d9/229e1fda8127e8558653b69e5245823aa16e770110897fceb712e621b41f/pyarmor_cli_core-8.1.1-cp310-cp310-manylinux2014_armv7l.whl", hash = "sha256:869ff6e51b8dd951620a22b5174d1d0795628f02c76df566d6790e7fbd68b6c9", size = 994953, upload-time = "2026-04-09T01:26:31.203Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/87/9f1849c2e44c93dbd04f685f76aaaac4731800750451d52622389747de59/pyarmor_cli_core-8.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c1d16c0069fec6daba35e384228e7d493ff7a83bd402bf3465527e2b8a1c1ad0", size = 643939, upload-time = "2026-04-09T01:26:36.522Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/16/4f48172435e6b91a323da5b97cda8fa77620d5e934b7ae5eb90690ecf80a/pyarmor_cli_core-8.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3dab4883b1ce7fcfa4212b94b7f5a538cc45a8a06742235caaf1b01f93b2b925", size = 439787, upload-time = "2026-04-09T01:26:41.738Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/2e/96233f232008021b1977c3a356da55af287a7f36760a28d60f7061616a1b/pyarmor_cli_core-8.1.1-cp310-cp310-win32.whl", hash = "sha256:00cc0266c245d946bdaf2e0f85047a5a4813bee2f26971d45faa833e2bf67500", size = 454720, upload-time = "2026-04-09T01:26:46.266Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/42/fa668d204c631c03157f71865514b14d0d400b7804f2ba9c4858d1379bac/pyarmor_cli_core-8.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:07584b1251aadc9540d47747bed0193131411399501a9d42ae74c7555dfb6a9e", size = 414449, upload-time = "2026-04-09T01:26:51.043Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/e3/173a37ad0d0ded7302037965c0ee747260b939a95299e9ebf0898c1e14bb/pyarmor_cli_core-8.1.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:852fd36f9854dacafa20f26a375adf0b94160cbff68d6a3b6970f5a79e7d3629", size = 511673, upload-time = "2026-04-09T01:26:57.916Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/33/2c23d8dfb7b6a5dd39899f3d298826ca02cdc231746966196666bbe33384/pyarmor_cli_core-8.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a31b7ec9a79c2286898830365d7cc7e275af4e3cc7f99e1717e622c797165fbe", size = 403721, upload-time = "2026-04-09T01:27:02.812Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/db/7aac19704813770f4acbcd6708d8afe181c21935fea1daed7621133a0fc6/pyarmor_cli_core-8.1.1-cp311-cp311-manylinux1_i686.whl", hash = "sha256:be3911a12c318b4cd93f7ffb492f3a8517c7e7c13b48dc53ea12988ef106ebb9", size = 484036, upload-time = "2026-04-09T01:27:07.129Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/70/73a9b625901da86b2590f2fd874ea6c1efd8f5ff195763261ba7e6248253/pyarmor_cli_core-8.1.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:8fba7ea6f2884ef78df89018bc7ca67fd3aba989dc4d3e6d88fc5e069a88ef9b", size = 451883, upload-time = "2026-04-09T01:27:12.465Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/dc/6afbee56cdfb8b1257bb9943782ce076c52bee546a532cb84541ec3179f5/pyarmor_cli_core-8.1.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:68d1e9bc7bc811a318965483dc079ce7da670799fc5ddb8ad1d3727216c437e3", size = 599055, upload-time = "2026-04-09T01:27:17.707Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/07/39a8cc9af7aabbe0d70ea62ec14d438bba5e88a69cc1be2f0dfa95bd8676/pyarmor_cli_core-8.1.1-cp311-cp311-manylinux2014_armv7l.whl", hash = "sha256:883c0ed2dfa19dab5a0c5edc8d8b4ad3b820491b1b4bbc4eaf5d4218a09caff5", size = 995541, upload-time = "2026-04-09T01:27:22.626Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/a4/efb2ebd451ee6bdda5c6ca359ab8a4bf7a0098cabc7d2d68d998ac9410b9/pyarmor_cli_core-8.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fedd378a0131d6df79e74e2cde4d039c7f19c14627301a7cba5a12ed200d55ab", size = 644557, upload-time = "2026-04-09T01:27:28Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/65/cc33d6423712a36aa6b133e9bb9483a2936ee44c1a40e0b99658cff45f2a/pyarmor_cli_core-8.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3dd40308a8bd0ccd8a4e7c162fd8093de6a6e1fefb5ded2825c339e58f15a996", size = 441387, upload-time = "2026-04-09T01:27:33.25Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/49/63509bc1b5d6bdb94306906a77638b4d4a2fdf6605244288990942b6ac50/pyarmor_cli_core-8.1.1-cp311-cp311-win32.whl", hash = "sha256:e5ce2e9442ff1a046cdd4a5c54c3df23640f37981877789420bf46d80943430c", size = 455036, upload-time = "2026-04-09T01:27:38.055Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/df/e0fd7f46bf412596d5cc3ffe6165e37d950b58aea4976bedc21e8c2a2419/pyarmor_cli_core-8.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d3f90b32d6b83c3bf73ff0f3d23f0045fc94671d6f467c9d50c641cea39795c", size = 414904, upload-time = "2026-04-09T01:27:42.651Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/b4/33fd5192cedc4600e9de8c9c7519205f421840da1b0df6e9491afca0a072/pyarmor_cli_core-8.1.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:4a6e09545fe74dc304992a0b002cc8d426c16ac3670d6f602d51838f080a2f31", size = 515480, upload-time = "2026-04-09T01:27:47.282Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/e5/3310cb2e29bab80bd923d7e0749c2163550effe1d1a6c6d5d95f5c58d183/pyarmor_cli_core-8.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:df035854a6603d4861a50156484027a03c01a7b9385b5365cabea4871ac72e3e", size = 407703, upload-time = "2026-04-09T01:27:51.869Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/66/1aa494d360a05fd90677db8c4b8385dedd2083fe96fe6607ea555f800c87/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux1_i686.whl", hash = "sha256:ec304457ff31b4939c8527640d7f525e78e9c91cc8307a11f068ef1291710b40", size = 489280, upload-time = "2026-04-09T01:27:56.492Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/ad/5f246234ce778dd3b7bd4faa2bded71690bbcddbf8f29506cba131ae4f85/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:9b65a62a71a88fb7534002067b972aac2f363e871ad34563579550fec1a260cb", size = 457685, upload-time = "2026-04-09T01:28:01.313Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/5a/cbaa2088483837ded4bb6301993de16743c633f230bf29947cdc10f7c307/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:d1c4e623dbb8bd93fc4273a7ca79d192618ca36cf74aceaeca9d36c83608ed77", size = 604340, upload-time = "2026-04-09T01:28:06.227Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/39/2686a96a3d80c5ef61fea10066677ddf3678bdd48afcec04a0d268203fa0/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux2014_armv7l.whl", hash = "sha256:0c10ad18ca2cb285b4b6c2bfdba6bc0234e84272f52bf692dce25ae7c6a21d56", size = 1001029, upload-time = "2026-04-09T01:28:11.426Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/e9/da903ea517387ac3d560b324fd03a7f50267cc2f68e99f84934fd9ee3ef9/pyarmor_cli_core-8.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:812201887aa2fb90f8b865a5dd443aa841cb951bd25d14dad843ce11b3ceef57", size = 649535, upload-time = "2026-04-09T01:28:16.373Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/eb/fcd742456b3a475e16eb5c3468132a9950095169cf1e6a64e4fb4bac208d/pyarmor_cli_core-8.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88e129603512326ce1440e3aa105506304424ee40366cc19f0a72a9aeeabecf7", size = 446443, upload-time = "2026-04-09T01:28:21.054Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/87/b58aed090a6a9debf65045f37e93b779e330c22106aa4cbb9aaaf7bb12b8/pyarmor_cli_core-8.1.1-cp312-cp312-win32.whl", hash = "sha256:b645d69c88f55a08d34fdd547759079ac1722e246d8e994ce3eb07937bdf318b", size = 460155, upload-time = "2026-04-09T01:28:25.665Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/b8/1dc8b15bc1607a27ed78025d5f50c97c78542930cab15a6d6d457b664bb3/pyarmor_cli_core-8.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:09df045973b5de85ecb950f3fffc23686caa5b43749d4d966ccd150719639e2b", size = 420620, upload-time = "2026-04-09T01:28:30.36Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/35/4b2969ebaea72869a5ba13847d47682ec77a1ff894fc83450e23e7257845/pyarmor_cli_core-8.1.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:86a576252f26752cfed5a1f0d6a12352f7ca8f38aadfd7e1df44453d933bf70c", size = 517354, upload-time = "2026-04-09T01:28:35.086Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/0f/c688f09d158d5d4bf3c83289b9d16d454d5ac001ae962a7501d4209dbcad/pyarmor_cli_core-8.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef4cd7ecf1aeca5748d221e548a6203f58ede46e3b1f31cc1fbba5170f1dc5c5", size = 409747, upload-time = "2026-04-09T01:28:40.159Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/f1/c97c393fa0f6431ac6537acd21f117025488b1254c255ae42da1809d2ae3/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux1_i686.whl", hash = "sha256:e95cfbaafee2eec3a3ef918c80f6ff6d6e26535c0b9f5ea615a97cf419a45603", size = 489761, upload-time = "2026-04-09T01:28:45.023Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/78/5874a8cecc9123475bcf5884037a6abd5859e4b93e89bc5eacc06dfd493f/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:2f42447c95f0de7df586a5899b395dcbf3193b5288a290f77dde90d4459700a6", size = 458105, upload-time = "2026-04-09T01:28:49.719Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/a0/73eb8557f0dd6f9910dfb1ff70ad31550d5b037dff78f80a006732efb085/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:635144791ddcf3a7bbd31ac10181828d40636e4b6a477ffefa0c6f4073524604", size = 604874, upload-time = "2026-04-09T01:28:54.871Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/6c/3eeab3e37748ae5354eb629ed5e03ee09c85c6abac8d77926d4d27507f52/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux2014_armv7l.whl", hash = "sha256:248bfb04d86f38851bdc13652d5e447e0415128c0cdfbb04498802e624a88475", size = 1001331, upload-time = "2026-04-09T01:29:00.195Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/f3/9736f5049e0fff5a333a8e14058e5b6b89ef68f469bcc23f9001fd47ab42/pyarmor_cli_core-8.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a71b3b4ab8d07259b82c9fdad18e099d86b0880bce352949b834db825cbd697", size = 651229, upload-time = "2026-04-09T01:29:05.072Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/e5/988ebde164710de231a93127a1df25a278307bb8cd31e75b207266a3ae8b/pyarmor_cli_core-8.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:bc51089e37602a4e4c75b990448c44e25d7b1c7048805ad789863c889e80793f", size = 447196, upload-time = "2026-04-09T01:29:09.99Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/5c/4be2fe6a77477c6a82b776c147487030978fb3c6c825dfcecafe4cdaf1d1/pyarmor_cli_core-8.1.1-cp313-cp313-win32.whl", hash = "sha256:689356f95fec04a53f91a624b0d1f4bfc8547e2ddede6799fb1c728377a51e19", size = 460848, upload-time = "2026-04-09T01:29:15.702Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/d1/80a1085e94a8bcc3f1d8c6d1ef703c1fc35afe9a8f2b5f0676d74fc1a80d/pyarmor_cli_core-8.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:2e7b259c4dda700360030cf212b2cca625aaf9e7498dc9bfcbee9ef85acfec9b", size = 421343, upload-time = "2026-04-09T01:29:20.354Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/57/12936c3ea69e88481e71d4f61a878aa49b26181c0fed9282b590f46b9132/pyarmor_cli_core-8.1.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:4bfbfe564881bd467b2103b47b4fdfe883746e39651545280f4c815d409e45f7", size = 522708, upload-time = "2026-04-09T01:29:25.471Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/4c/ba326b377d0d698fa80864a478822e56acd0b1dd74db7b2645ca66e3c732/pyarmor_cli_core-8.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0ff3494acdacbec1565a1d98065ab2a2b47683778ad02b89b643008a1ffe90bd", size = 414399, upload-time = "2026-04-09T01:29:30.291Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/15/f6121674cca8487665bc4803da745a1f4e7f98d233bf33e659ca3dbfec29/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux1_i686.whl", hash = "sha256:2d905427cd23226f86998a28f6c79c849136dfb85e569e32b208ad7d75b24853", size = 495367, upload-time = "2026-04-09T01:29:35.223Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/6a/67121dc6f489120322bf4742774a3ebd05eb4b6d1ebcf971b34ea91ec2b4/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux1_x86_64.whl", hash = "sha256:4de138aacd465e0a0d4601b290d0555d47657f59b86d5eeec1bc660fd01e149a", size = 462060, upload-time = "2026-04-09T01:29:39.799Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/87/09d5c023474495dd3dbb789ecf538af1bcbe53c49cd07a703cfa5da03bde/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux2014_aarch64.whl", hash = "sha256:1777c7c2c7130a34b7c7125ddd828569e3f938fc91efc0db99fb0ee1eba88aa4", size = 609493, upload-time = "2026-04-09T01:29:44.322Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/55/c6f403c7a33900d1a194839e55677814a35ab33d10c4234394f7ad12c311/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux2014_armv7l.whl", hash = "sha256:5172681e95bfbf4eb250d53e1ede75b270e7ec7c29a4accfb0acf766bc7b6351", size = 1004962, upload-time = "2026-04-09T01:29:49.616Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/bc/ce7146830a08306884929b53f785bb708df7c360a73a2a3687ff73533918/pyarmor_cli_core-8.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:06de0a47cb8ae86fe9270f3930d8e245fff73f87bd3c49cdc2af47c8ce1a5b06", size = 655381, upload-time = "2026-04-09T01:29:54.994Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/05/0fac5b9e9d37e1ea46ceae2733ab07d348b199257d99f69d290eae363ac0/pyarmor_cli_core-8.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7ae1c265c6560e869d355373fd86c2880a951db6bbf2bbdffec698d47c269be9", size = 450987, upload-time = "2026-04-09T01:30:00.065Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/84/173c89753b13037ad45fd6c220aeb12e055de261c8453025df9dc0c82b2c/pyarmor_cli_core-8.1.1-cp314-cp314-win32.whl", hash = "sha256:18a7f8aa4054395be9fe5b051727f2c32bdce74d609ef75cb869a9fd0396da90", size = 465415, upload-time = "2026-04-09T01:30:04.793Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/ce/32fb60e0971791c7e102ab87140d8c5da444e8b22b05614d7b63b7a90113/pyarmor_cli_core-8.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:9e50864097734b039060074b4913ffca73069162ff3c32556f3b594888129661", size = 424573, upload-time = "2026-04-09T01:30:09.704Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/02/7632897343d0b1ac3faa6e882f62880c4910d1331a46f1796816b99e9f8d/pyarmor_cli_core-8.1.1-cp315-cp315-macosx_10_14_x86_64.whl", hash = "sha256:eb3a63691a67aca10e2af7937baf0c88effc459c78c3c4ef1c48cf562376b830", size = 522819, upload-time = "2026-04-09T01:30:14.265Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/aa/09cce8ed725419d8a3c1a4a4b588b5f369094ef0b9768bc7e2d7fe855897/pyarmor_cli_core-8.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:75328d808fb790546272ba92440c8ae9e80f3c84cce3f423fefb00a9af230af0", size = 414398, upload-time = "2026-04-09T01:30:18.603Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/a9/844a2fcb09dc30994e03b5469ed80d6066e794828cd48af437da33029f8d/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux1_i686.whl", hash = "sha256:e0e0965e8a1d779485f747b93d6f4c7a20395553851921b0fd22ea89fb09287e", size = 495322, upload-time = "2026-04-09T01:30:23.59Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/1b/b169c06628dc9ac375c5d3f270cd742e344ddfa6dab5e00216a984ccfa01/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux1_x86_64.whl", hash = "sha256:3562142724c8cd48a6b161b35a7b4ebfc30820f08ca0adc28f8729077531c3f4", size = 461861, upload-time = "2026-04-09T01:30:28.655Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/98/8ab28d635e03d269234243c4ac2fd82d722435640d9be89e78cb6f4ba15f/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux2014_aarch64.whl", hash = "sha256:27c6e269131da709bcf1aac04ed8285eaa8a328f7682e98c4e93a81f97e2e4db", size = 609340, upload-time = "2026-04-09T01:30:33.652Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/68/f6e381d81e988077c6fdddcfa659ed30953640e863928575886815e1eefa/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux2014_armv7l.whl", hash = "sha256:0cc8d7c337663eb9d108649ba296973284ff8b6fc0f83ae0ce347697533a983d", size = 1004837, upload-time = "2026-04-09T01:30:38.944Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/83/4958e7eb4a4dd27b024a034443636e7db418d06ee743eb09eecab2360e18/pyarmor_cli_core-8.1.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:00cccda40da819c45550625c910f836b9c768699ea40b04c058c3221270b514b", size = 655276, upload-time = "2026-04-09T01:30:44.053Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/d8/c8992fc6f66fcd8b29f8497ca352165bd9e0972a1f5371eb8c01aaca7a3e/pyarmor_cli_core-8.1.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:488be5b88a0c434fb17457972f6b7f681b38650ce078c3f19dced5b15091146d", size = 450926, upload-time = "2026-04-09T01:30:49.022Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/61/6cb081493c75662dd979a6cf8f6092d81605a9a59c2439a2ab77f3abfc1b/pyarmor_cli_core-8.1.1-cp315-cp315-win32.whl", hash = "sha256:a0f087fe8c388d178859dbc2a14e11e12bf8efc694f035e41d1dfc5b4a630052", size = 465352, upload-time = "2026-04-09T01:30:53.861Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/87/9daf968e6e6add10d089eef86b46a26c9908bfae74820bc97aacbf37d081/pyarmor_cli_core-8.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:c1b96471bb1d9fc575ba526841829c6bf03cd9db5caabf5688518a29ed389870", size = 424475, upload-time = "2026-04-09T01:30:58.457Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/5c/e653770e60de273502616202ffcf769011a50c744442bd63e01506ee61d6/pyarmor_cli_core-8.1.1-cp37-cp37-macosx_10_14_x86_64.whl", hash = "sha256:6d41880b098558f3905a18ffa3c9498e95ec71d93ddb3bc2f23701947e174862", size = 509125, upload-time = "2026-04-09T01:31:03.266Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/71/da506483c853b23eb45e63678da67919c113894649f6fd7fa08a602447ce/pyarmor_cli_core-8.1.1-cp37-cp37-manylinux1_i686.whl", hash = "sha256:05d9c9c54636b09fcbb37a95c97ccb1df9718f0cad88e03eb72f12bf18922092", size = 479615, upload-time = "2026-04-09T01:31:08.596Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/fa/249684e417a9fcdd369672232901dfad7fe84b8a7ed0e3c12457ef6f108e/pyarmor_cli_core-8.1.1-cp37-cp37-manylinux1_x86_64.whl", hash = "sha256:245e837ab2543ce7e2eaa39a80c3ef093ff97a51ced18ddb9052512859f8db72", size = 449406, upload-time = "2026-04-09T01:31:13.597Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/06/c41929814430426aa4f9a24ef736b210e8b2482d7bcc84ca0fa64bd577c6/pyarmor_cli_core-8.1.1-cp37-cp37-manylinux2014_aarch64.whl", hash = "sha256:bf023d2f6c0cf69013eb80da97da87e8a67c0b2dd1e6d3375f02e0f195b3524f", size = 592931, upload-time = "2026-04-09T01:31:19.038Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/fe/74bc6a0d3a3f41696f4a944872bca0c73aa124ddef2b8af8168fb11337c1/pyarmor_cli_core-8.1.1-cp37-cp37-manylinux2014_armv7l.whl", hash = "sha256:2a66cfed9f7dd72043389ed9b933ae57ffb00d28a8195f4d6aa446a8eda0a787", size = 989241, upload-time = "2026-04-09T01:31:24.261Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/f2/343507785aa9d742589a57006cecb5fe8f5552bd05c3076712efffeedd30/pyarmor_cli_core-8.1.1-cp37-cp37-musllinux_1_1_aarch64.whl", hash = "sha256:183555f51b42d3be95b7fa248800ddfec6bcdc7ea843cfb4982be2671df8c39f", size = 639618, upload-time = "2026-04-09T01:31:29.486Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/04/ae296dabb653962116cd9c7bb453b9d12fac8efa49bb67e2e98c15aec062/pyarmor_cli_core-8.1.1-cp37-cp37-musllinux_1_1_x86_64.whl", hash = "sha256:6575a73f3928782e3394bd4d74942fa60f926a3f78bcca5bb920b33e49647910", size = 435876, upload-time = "2026-04-09T01:31:34.288Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/b5/f3647e1d1d7d58d12257d23bec0ee37838bc359a77b051fb724002283fc4/pyarmor_cli_core-8.1.1-cp37-cp37-win32.whl", hash = "sha256:6129c315fac01be1ceae09231816a42396d00928a96f537faf1ba3435263ee67", size = 447840, upload-time = "2026-04-09T01:31:39.095Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/a0/b767e2303491a5028be332c5c38656e1e6953c224c11f51ef7ac98f0d3a9/pyarmor_cli_core-8.1.1-cp37-cp37-win_amd64.whl", hash = "sha256:84a8ad19400f52e75451c35a04e41d8034bbfdf8743a306346a800a995a5f470", size = 407502, upload-time = "2026-04-09T01:31:44.021Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/9c/dc11b51400a3c9ae97d092b169dbcbe5d3cdd7a48efa8253f173cec336a0/pyarmor_cli_core-8.1.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:db8b83e5d656ce2310a252ecb009f84ebeaf8c60be6a9b5fb6a7e08ac3dc0064", size = 510970, upload-time = "2026-04-09T01:31:48.658Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/84/c2a1832659e83ced41974a64b3317d80c72126b38a61314419dc0c3f578d/pyarmor_cli_core-8.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8b397fcd7febfcf692daa5641ee175377840304cbc0456929c84bf0eeefc634e", size = 483313, upload-time = "2026-04-09T01:31:53.644Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/0b/df3cd01216f01d6122447e7d69b8562973a27e9df15c2cf0840872e30763/pyarmor_cli_core-8.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b487b9e93a5e63f3e1ff7da2af6ef0db79aa4a92234613d8e7c93ca8239f525a", size = 460867, upload-time = "2026-04-09T01:31:58.436Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/33/800fbb62b8a7193ddba3445a117900782125ec678b086bc786c0ff4ff6c9/pyarmor_cli_core-8.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0838f354a4c299bf6204a5be510b193c37aa4b4bd2ed2ebd23046b0b75a345db", size = 597415, upload-time = "2026-04-09T01:32:03.52Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/7e/927a29f880ec35eef6499470642c079417d1ef6caa4c887a05d982a1cf22/pyarmor_cli_core-8.1.1-cp38-cp38-manylinux2014_armv7l.whl", hash = "sha256:a2dd5be9b02b3b4cdc300423bdb38453e34bef7c5397d36981c35280d0ea13bd", size = 993141, upload-time = "2026-04-09T01:32:08.932Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/fb/d33f4128745823235f3d6f2488f1161f43d9f855c730abd598978e97b7c0/pyarmor_cli_core-8.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:08bb1e1071cb4356ce85ef8e7089cf85853cd3204dfb146a00f9401bca80a3a1", size = 644146, upload-time = "2026-04-09T01:32:13.936Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/ba/addbf681e1fcb006a9590b721beac1bbe663553dc151d3b146a5a51cabc0/pyarmor_cli_core-8.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a15fc2e4af49ffca703391a7adf29f39c8975f09afafd2461ad808dc2aeaf215", size = 439049, upload-time = "2026-04-09T01:32:19.879Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/97/a92a86b584499d4d8dc3ccd94ca92048892242d2d6fce84ba27bf66344f3/pyarmor_cli_core-8.1.1-cp38-cp38-win32.whl", hash = "sha256:b685e5cdd68660dc6fbcf1502b79a93ccd7969a5d411837d3dc4fa8f266df81a", size = 452679, upload-time = "2026-04-09T01:32:24.782Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/fc/b933559c1fccacc063bc04cd88d7cc48c84a1fb707ec03fe0b106e34337e/pyarmor_cli_core-8.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:a83d4d571012bffe2196a1b65f56abf04ee57dd09aa8d063ced99b0cbc21c341", size = 413687, upload-time = "2026-04-09T01:32:29.485Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/48/07a43938c9fb667d1b487aedf4ef691914cb049c398eb355585ad7d691ad/pyarmor_cli_core-8.1.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:a7dee28cefefc8e2c183e8b56a12d4029b9517f4f860ec17bed3cca5f495be62", size = 510309, upload-time = "2026-04-09T01:32:34.533Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/ed/ce279cdd805d22c0f315eebe1c41d541e0e3bd5f58057297102912fc38de/pyarmor_cli_core-8.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4eedb4151c13c8fa55a0c2050a1f06513a8099c78983a99f5176b962892bf85", size = 402366, upload-time = "2026-04-09T01:32:39.294Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/9e/841f564ad60e0d9a0ba05c416f1220b95038fe643f08bc5871c932d3549d/pyarmor_cli_core-8.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3122cb3b9049d8f71bc459abdd7baa3a588d49a7cb2d20aaaf9d01e8b16ea84b", size = 481480, upload-time = "2026-04-09T01:32:44.122Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/bf/d019a15c35f2c06a971c4140c471249d4da523afb37aed70855d80bf9e12/pyarmor_cli_core-8.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fa41bb83dd13fdac97f11703afabe241253906ed233c39d5f2248a64a17a5efc", size = 461013, upload-time = "2026-04-09T01:32:49.154Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/c2/8c4d7314e1fc5262fa47ff1ae61eec421261e1160329cd940f4f463a590e/pyarmor_cli_core-8.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0817f5493745b0521f437ad26713bdf892a102e86f2e482b73f73fdd1432c605", size = 598571, upload-time = "2026-04-09T01:32:53.983Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/19/21dd64c879c3c15a960393ffbce3b6c15c8e760e1d7d99a284f19aabfab1/pyarmor_cli_core-8.1.1-cp39-cp39-manylinux2014_armv7l.whl", hash = "sha256:ccf8db789066281475faedd5b06253b28bed3316a1bce5716e84166604b3ca10", size = 994367, upload-time = "2026-04-09T01:32:59.448Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/2e/1e2fcd7779f376af4c3a8ede0c9e9ae9981d6e735a8525eee566c9d80329/pyarmor_cli_core-8.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b98390156ab4226d77dcc4e7b6ea79ce82ffd553aaf12b402b965468d0c30a4e", size = 643749, upload-time = "2026-04-09T01:33:04.734Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/35/4ee9c4315aef4f28f9cfb47a50d4e48bfec220d77f5e1e86a36d50302b33/pyarmor_cli_core-8.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:27d776ab86890c84381b3eed7c5906550f929d11d3ed63b4d981e0b4670a7f94", size = 439652, upload-time = "2026-04-09T01:33:09.45Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/0e/06f20cb41f14e703cf78b68e37a25f2fa163f87a82914404d9c916437a92/pyarmor_cli_core-8.1.1-cp39-cp39-win32.whl", hash = "sha256:5d7a3b90c559a29596db18d7f99342da1ba4634d526e08cbbe4743d4c77e3679", size = 454449, upload-time = "2026-04-09T01:33:14.388Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/75/681dfabc50249cb38f452c0139006d6df9302dc5e32925972506f31ec1fa/pyarmor_cli_core-8.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:8dcd779236cb32cae03f2c71c469fbb945e45c6ef04e38a7ad39e412fe7a8df0", size = 414029, upload-time = "2026-04-09T01:33:19.46Z" },
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/90/89c95f00699600a2c476b1c80e97df5ea1f09b995a8abfc0a8f118f0bf38/pyarmor_cli_core-8.1.1-py3-none-any.whl", hash = "sha256:06eb9771c7d1f0ad7c00884f13ea42edfbffab8b28ef718ce197c0b4c69d97d5", size = 5177, upload-time = "2026-04-09T01:33:22.702Z" },
]
[[package]]
name = "pycparser"
version = "2.21"