把任意项目(任意语言、任意嵌套层级、任意文件类型)打包成一个加密容器。只有持有正确口令的人才能解开;脚本语言项目甚至可以不解密到磁盘、输入口令后直接运行。
像压缩包一样通用(什么都能装),但像加密保险库一样安全(没有口令打不开),还能让脚本项目在“全程不出现在磁盘上的情况下”跑起来。
你有一个项目(Python / Go / Java / Node / C++ / 混合均可),想:
- 静态保护:代码在硬盘上是密文,别人偷走文件也看不到源码
- 可控分发:部署到客户/合作方环境时用口令控制访问
- 透明运行:脚本语言项目只需要输密码就能启动,源码全程不写磁盘
- 单文件交付:整个项目目录树打成一个
.enc文件
# 1. pack — 打包加密
python3 pack_project.py pack /path/to/my_project -o myproject.enc -e main.py
# 2. run — 直接运行(脚本语言项目全程不解密到磁盘)
python3 pack_project.py run myproject.enc
# 3. unzip — 解密恢复到磁盘
python3 pack_project.py unzip myproject.enc -d ./restored_project
# 4. list — 查看包内文件清单(不泄露源码)
python3 pack_project.py list myproject.enc| 入口类型 | 自动识别 | 已验证 |
|---|---|---|
main.py / app.py 等 Python 脚本 |
✅ 后缀 .py / .pyw |
✅ |
server.js / app.js 等 Node.js 脚本 |
✅ 后缀 .js / .mjs / .cjs |
✅ |
app.ts TypeScript 脚本 |
✅ 后缀 .ts,优先 tsx,回退 ts-node |
✅ |
start.sh / .bash / .zsh / .fish Shell 脚本 |
✅ 后缀 | ✅ |
runner (无后缀 shebang 脚本) |
✅ 魔数 #!/ |
✅ |
svc (Go / Rust / C / C++ 编译产物) |
✅ ELF 魔数 \x7fELF |
✅ |
| macOS Mach-O 二进制 | ✅ 4 种 Mach-O 魔数 | 检测逻辑已写,Linux 环境未实跑 |
Windows .exe |
✅ PE 魔数 MZ,Linux 下尝试 wine |
检测逻辑已写,未实跑 |
app.jar Java 归档 |
✅ 后缀 .jar → java -jar |
✅ |
script.rb / script.lua / script.php / script.pl |
✅ 后缀 | 路径探测已写,需本机有对应解释器 |
| 不可识别的无名入口 | --runner 手动指定 |
✅ |
所有外部命令(
node/java/ruby/bash等)都会在运行时自动从PATH、~/.sdkman、~/.nvm、~/.cargo、/usr/local/bin等目录里找绝对路径,而不是依赖裸命令名。
python3 pack_project.py pack <项目目录> -o <输出.enc> [-e <入口文件>]- 递归收集项目目录树里的所有文件
- 自动跳过
.git/__pycache__/node_modules/.venv/dist/build等无需打包的目录 - 没指定入口时按
main.py→app.py→index.js→server.py→manage.py→app.js顺序自动探测 - 口令用交互式 getpass 输入(非自动化场景可用
PYSHIELD_PASSWORD环境变量绕过)
容器格式(.enc 文件)
[offset size content]
0 64 Header (magic `CODECRYP` + version + salt + iv + manifest_len)
64 var Encrypted Manifest (AES-GCM(zlib(JSON)))
└── manifest 结构:
{
"version": 2,
"entrypoint": "main.py",
"files": [
{
"path": "src/main.py",
"size": 1234, # 原始文件大小
"mtime": 1735828800, # 修改时间
"mode": 33261, # 权限位 (0o100755 等)
"iv": "hex...", # 每个文件独立 96-bit IV
"data": "hex..." # AES-GCM(zlib(source))
}, ...
]
}
- 每个文件压缩后独立 AES-GCM 加密,IV 各不相同
- 口令通过 PBKDF2-SHA256(20 万次迭代)派生 256-bit 密钥
- 文件权限(
st_mode)完整保留,ELF 二进制 / shebang 脚本还原后直接可执行
python3 pack_project.py run <文件.enc>运行流程:
- 解密 manifest,按入口后缀/魔数选择执行器
- 把包内所有文件解密到内存 (
/dev/shm),自动避开noexec挂载点 - 启动入口进程(如
python -m main、node server.js、java -jar app.jar) - 入口进程退出后,自动
shutil.rmtree清理临时目录 - 入口进程的退出码原样透传给调用者
全程源码不写入持久磁盘,进程退出即销毁。
退出码行为:子进程 exit=0 → 返回 0;子进程 exit=N → pack_project.py 同样 exit=N,便于写 shell 编排脚本(&& / ||)。
找不到执行器时:默认停驻并显示临时解密目录,等用户按 Ctrl+C 后自动清理;或加 --runner 手动指定:
python3 pack_project.py run myproject.enc --runner bash start.sh
python3 pack_project.py run myproject.enc --runner /usr/bin/qemu-x86_64 ./app.bin{work} 会被替换成临时目录绝对路径。
python3 pack_project.py unzip <文件.enc> -d <目标目录>完整还原:文件内容、相对目录结构、修改时间、权限位。便于:
- 部署前临时解密给 Docker COPY
- 构建阶段需要磁盘可见源码的场景
- 项目交接、归档恢复
python3 pack_project.py list <文件.enc>显示:版本、入口、文件数量、每个文件的相对路径和原始大小。不泄露任何源码。
# 1. 打包 (在开发者机器上执行一次)
python3 pack_project.py pack ./my_python_project -o deliver.enc -e src/main.py
# 2. 把 deliver.enc 发给客户
# 3. 客户运行 (只需要 Python 3.8+ 和 cryptography 库)
python3 pack_project.py run deliver.enc
# 输入口令 → 项目正常启动,源码全程不落地# 入口是 Python, Python 内部用 subprocess 调包内的 Go 编译产物
python3 pack_project.py pack ./fullstack -o fullstack.enc -e backend/api/main.py
# 运行时: Go 二进制自动找到, 退出码正常透传
python3 pack_project.py run fullstack.encexport PYSHIELD_PASSWORD="SuperSecret"
python3 pack_project.py run deliver.enc && echo "启动成功" || echo "失败, exit=$?"# CI/CD 中: 解密到临时目录 → docker build → 清理
python3 pack_project.py unzip deliver.enc -d ./build_ctx
docker build -t myapp ./build_ctx
rm -rf ./build_ctx| 能力 | 说明 |
|---|---|
| 加密任意文件 | 源码、二进制、图片、配置、文档均可打包 |
| AES-256-GCM | 认证加密,篡改会被检测到 |
| PBKDF2 20 万次迭代 | 抗离线字典攻击 |
| 每文件独立 IV | 重复明文不泄露信息 |
| 口令直达内存 | 口令本身不落盘 |
| 临时目录 RAM + 清理 | 默认 /dev/shm,退出即销毁 |
| 场景 | 原因 |
|---|---|
docker compose up --build 运行时解密 |
builder 必须获明文才能 COPY 进镜像;需镜像层加密 (dm-crypt / containerd image encryption) |
| 常驻 daemon / systemd service | 临时目录清理后进程会挂掉;落地运行需先 unzip |
| 防 root 用户读取内存 | 运行时 root 总能 coredump / ptrace;需机密计算 (Intel SGX / AMD SEV) |
防带 noexec 的极端环境 |
自动 fallback 到 /tmp,但若所有 tmpfs 都 noexec 则 ELF 二进制跑不了 |
脚本语言安全性补充:Python / Node 等虽在内存里解密,但解释器的 inspect.getsource()、pdb、__pycache__ 等机制仍可能泄露。PyShield 防的是「静态文件泄露」和「运行时源码落盘」,不是「运行时内存取证」。如果需要防内存读写,需配合解释器加固或编译为 .pyc/原生扩展。
pip install cryptography仅此一个第三方依赖。Python ≥ 3.8 自带 hashlib / zlib / struct / json / tempfile / subprocess。
找不到 node / java / ruby 怎么办? 工具会自动搜索以下目录:
~/.local/bin~/.cargo/bin~/.sdkman/candidates/java/*/bin~/.nvm/versions/node/*/bin/usr/local/bin/opt/homebrew/bin
让用户级安装(SDKMAN for Java、nvm for Node)开箱即用,无需系统级 PATH 配置。
开发阶段对每个声称的能力做了端到端实跑验证(非只看返回的字符串)——构建真实项目 → 打包 → 删除源码 → 运行 / 恢复 → 校验输出和退出码。
| 场景 | 验证依据 |
|---|---|
| Python 入口 + subprocess helper.sh | 输出 HELPER_OK |
Python 深层包 pkg/main.py (含 __init__.py) |
输出 PKG_OK |
| Python exit=7 → shell exit=7 | 透传退出码 |
| Node.js server.js | 输出 NODE_ENTRY_OK |
| Bash start.sh | 输出 BASH_ENTRY_OK |
shebang 无后缀 runner |
输出 SHEBANG_RUNNER_OK |
LLVM clang 编译的真实 ELF 二进制 svc |
输出 ELF_OK |
javac 编译的真实 JAR (java -jar) |
输出 JAR_OK |
| Python → exec Go 二进制 → 退出码 42 | 子进程退出码原样透传 |
| 管道通信: producer → consumer | 3 条 MSG 全部到达 |
| 并发子进程 + wait + 任一失败则退出非零 | 返回 worker 中的最大退出码 |
| 492 文件 / 深度 8 级的大型项目 | pack 0.11s、unzip 0.09s、run 0.11s |
--runner 指定自定义解释器 |
输出 CUSTOM_RUNNER_OK |
| 加解密 list / unzip | 正常识别 |
| 错误口令 / 篡改 / 非 .enc 魔数 | 全部拒绝 |
测试过程中发现的 5 个真实 bug 已全部修复(详见下文「开发记录」)。
| 现象 | 根因 | 修复 |
|---|---|---|
| 子进程 exit=N, pack_project.py 自身却 exit=1 | subprocess.check_call 在 exit≠0 时 raise 异常,被顶层兜底为 traceback |
改为 subprocess.call + sys.exit(exit_code) |
run xxx.enc --cmd ... 报 KeyError: None |
--cmd 命令行参数名覆盖了 argparse 保存子命令名的 .cmd 属性 |
参数重命名为 --runner |
_detect_executor 发出的 java/node/ruby 报 FileNotFoundError |
工具在非交互 shell 里找不到 SDKMAN/nvm 装的命令 | 所有命令用 shutil.which 解析成绝对路径 |
同样找不到 java,但 ~/.sdkman 明明有 |
代码写了 ~/sdkman/... 漏了 . 前缀(真实目录是 .sdkman) |
修正为 ~/.sdkman/... |
| shebang 脚本 / ELF 二进制还原后报 PermissionError | pack 只存了 size/mtime,没存 st_mode |
打包时记录 mode,还原时 os.chmod |
ELF 二进制在 /dev/shm 启动报 Permission denied |
生产环境 /dev/shm 几乎都挂载为 noexec,execve 被内核拒绝 |
写入 shebang probe 实测 exec 可用性,失败自动 fallback 到 /tmp |
Q: 支持多大项目? A: 492 文件 / 深度 8 级的项目端到端 < 0.2s。1000 文件以内随项目规模近线性增长,未遇瓶颈。
Q: 口令丢了怎么办? A: 没办法。PBKDF2+AES-256 目前不可暴力破解。请妥善保管口令。
Q: 能打包 .git 目录吗?
A: 默认跳过 .git、__pycache__、node_modules、.venv、dist、build、.idea 等。源码库里的 .git 不应打包。
Q: 能和其他压缩工具混用吗?
A: 不能。.enc 是自定义二进制容器,zip / tar / 7z 都无法识别。这是设计如此——防止别人用常见工具暴力尝试解压。
Q: 为什么 Python 项目运行时未见源码文件?
A: run 把文件解密到 tmpfs(内存文件系统)。进程退出后 shutil.rmtree 删除,源码全程不在持久磁盘停留。
Q: Windows 能用吗?
A: pack 和 unzip 跨平台。run 在 Windows 上需 bash / node 等解释器,.exe 入口能识别但在 Linux 容器里未实跑验证。
jiami/
├── pack_project.py # 主工具: pack / run / unzip / list
├── cryptolib.py # 运行时加载器 (Python import hook, 透明 import)
├── run_encrypted.py # Python 项目运行时启动入口模板
├── custom_codec.py # 单文件加密工具 (无打包概念的轻量场景)
└── README.md # 本文档
如果你要构建的是常驻 Python 服务(不想每次清理临时目录),可以嵌入自定义 sys.meta_path finder:
from cryptolib import EncryptedPackage, run_entrypoint
pkg = EncryptedPackage("myproject.enc", "password")
run_entrypoint(pkg)这会让 Python 的 import 机制在每次尝试导入模块时,动态地从 .enc 中解密对应文件到内存后执行。所有模块都在内存中编译运行、不写磁盘。限制:仅适用于纯 Python 项目,不能加载 C 扩展(.so)。
只需加密单个文件,不需要项目管理?用这个:
python3 custom_codec.py pack secret.py -o secret.myc
python3 custom_codec.py unpack secret.myc -o ./output_dir/secret.restored自定义魔数 MYCODE01,自定义扩展名 .myc。
本工具按 "AS IS" 基础提供。加密强度依赖于口令质量——请用 16 位以上随机口令。 任何加密工具都无法保证在所有场景下的绝对安全。请结合具体威胁模型选用防护层级。
发现问题建议在对应仓库开 issue,附:
- 入口类型和完整错误输出
pack_project.py list your.enc输出(不泄露源码)- 是否已安装
cryptography(pip show cryptography)