PHP Decrypt 是一个 PHP 扩展,在文件编译和字符串编译入口捕获源码,用于分析运行时交给 PHP 编译器的代码,包括 eval() 接收到的代码字符串。
- 按项目相对路径保存最近捕获的文件,保留源码原始字节。
- 独立保存
eval()代码片段,不添加 PHP 标签或拼接片段。 - 通过 SHA-256 对历史内容去重,以 JSONL 索引记录版本变化。
- 提供并发写入保护、完整性检查工具和可运行的捕获示例。
能否捕获到明文取决于目标代码、加密扩展的实现和加载顺序。本扩展不保证还原 opcode、消除混淆或恢复未执行的代码。
- Linux 或 macOS,PHP CLI 及对应的
phpize、php-config开发工具。 - PHP 的
hash、json扩展。 - C 编译器、make、autoconf;macOS 另需 Xcode Command Line Tools。
当前主要验证范围如下,完整补丁版本、测试结果和限制见运行环境验证。
| 平台 | PHP | 已验证的运行方式 |
|---|---|---|
| Linux x86_64 | 7.4 / 8.3 | NTS CLI、FPM;另有 Linux 8.3 ZTS CLI 和真实线程验证 |
| macOS Apple Silicon(arm64) | 7.4 / 8.3 | NTS CLI、FPM、ZTS CLI;8.3 另含真实线程验证 |
| macOS Intel(x86_64) | 7.4 / 8.3 | NTS CLI、FPM |
普通 CLI 和 FPM 场景建议使用 NTS。编译扩展时,PHP 版本、架构和 NTS/ZTS 构建必须与目标运行时匹配,不能混用 .so。
原生 Windows 不属于支持范围;Windows 用户可在 WSL2 或 Linux 容器内按 Linux 流程使用。config.w32 和 Windows 条件编译代码保留给自行适配的开发者,不代表已验证 Windows 构建。
先安装目标 PHP 对应的开发工具和上述构建依赖,再执行:
git clone https://github.com/Albert-Zhan/php-decrypt.git
cd php-decrypt
phpize
./configure --enable-decrypt --with-php-config="$(command -v php-config)"
make -j2
sudo make install构建产物为 modules/decrypt.so;make install 将其安装到所选 PHP 的扩展目录。安装目录属于当前用户时,可省略 sudo。测试方法见测试与开发。
安装 Xcode Command Line Tools 后,可用 Homebrew 准备工具链。以下以 PHP 8.3 为例,适用于 Apple Silicon 和 Intel:
brew install php@8.3 autoconf pkgconf
export PATH="$(brew --prefix php@8.3)/bin:$PATH"随后执行上面的源码编译命令。多版本环境中,确保 php、phpize、php-config 来自同一套 PHP 安装。
以下命令从仓库根目录执行,临时加载刚构建的模块,无需先修改 php.ini。将 /path/to/project、/path/to/captures 和入口 index.php 替换为实际路径;输出目录应位于项目和 Web 根目录之外,并由运行 PHP 的用户独占管理。
PROJECT_ROOT="$(cd "/path/to/project" && pwd -P)"
mkdir -p "/path/to/captures"
chmod 700 "/path/to/captures"
CAPTURE_ROOT="$(cd "/path/to/captures" && pwd -P)"
php -d "extension=$PWD/modules/decrypt.so" \
-d decrypt.switch=enable \
-d "decrypt.project_root=$PROJECT_ROOT" \
-d "decrypt.save_path=$CAPTURE_ROOT" \
-d opcache.enable_cli=0 \
"$PROJECT_ROOT/index.php"如果已在 INI 中加载扩展,请省略命令中的 -d "extension=...",避免重复加载。扩展仅捕获实际经过编译入口、且来源位于项目内的代码。要捕获其他执行分支,需要运行对应入口和输入;这会执行目标程序的业务逻辑,请在可控环境中操作。
macOS 的 /tmp、/var 可能经过系统符号链接。上面的 pwd -P 会取得物理路径,例如 /private/tmp/...,以满足输出路径的符号链接保护规则。
长期使用或接入 FPM 时,可参考 decrypt.ini.example,将以下内容加入目标 PHP 的 php.ini 或扫描配置目录:
extension=decrypt.so
[decrypt]
decrypt.switch=enable
decrypt.project_root=/www/shop
decrypt.save_path=/data/php-captures/shopextension=decrypt.so 适用于已执行 make install 的情况,也可填写模块绝对路径。两个目录必须提前创建,输出目录须对 PHP 运行用户可写;建议权限为 0700,每个项目使用独立输出目录。
| 配置项 | 默认值 | 说明 |
|---|---|---|
decrypt.switch |
disable |
设为 enable 才启用捕获钩子 |
decrypt.project_root |
空 | 启用捕获时必填,已存在的项目绝对目录 |
decrypt.save_path |
空 | 启用捕获时必填,已存在且可写的输出绝对目录 |
decrypt.history |
1 |
保存按内容去重的历史对象 |
decrypt.index |
1 |
追加 JSONL 版本记录,供检查和恢复工具使用 |
decrypt.capture_eval |
1 |
捕获来源位于项目内的字符串编译内容,包括 eval() |
decrypt.exclude_paths |
[] |
JSON 数组,排除项目相对路径及其子树 |
decrypt.max_capture_bytes |
0 |
单份文件或 eval 体的字节上限,0 不限,支持 K / M / G |
decrypt.log_skips |
0 |
将排除、超限原因写入 PHP 错误日志,每个请求对同一来源、类型和原因仅记一次 |
decrypt.sync |
0 |
同步文件和目录更新,提高持久性并增加写入延迟 |
所有配置均为 PHP_INI_SYSTEM,不能用 ini_set() 修改。CLI 可使用 -d;修改 FPM 配置后需重启或重新加载 FPM。CLI 与 FPM 可能使用不同配置文件,php --ini 和 php --ri decrypt 仅检查当前 CLI。
输出路径受 open_basedir 限制,拒绝已有符号链接。输出目录不能等于项目根目录,也不能是项目根目录的上级目录;若置于项目内部,该输出子树会被排除。输出目录及其父目录应由可信用户控制。
从旧版本升级时,需新增 decrypt.project_root。0.2 起不再写入 decrypt.code,旧文件不会自动转换或删除。
默认捕获范围和大小不受新配置限制。需要排除依赖、缓存目录或限制单份内容大小时,可增加:
decrypt.exclude_paths='["vendor/","storage/cache/","bootstrap/cache/"]'
decrypt.max_capture_bytes=2M
decrypt.log_skips=1路径相对于 decrypt.project_root,按完整路径段匹配;vendor 与 vendor/ 等价,不匹配 vendor-old/。单文件条目也可使用,例如 config/private.php。排除规则同时作用于来源文件及其产生的 eval;只支持字面路径,不支持通配符或正则。空条目、绝对路径、NUL、空路径段和 . / .. 路径段均为非法配置。带单引号的名称可在 JSON 中写为 \u0027;POSIX 文件名中的反斜杠按 JSON 转义为 \\,不会当成目录分隔符。
上限按实际送入捕获入口的字节数计算,2M 为 2,097,152 字节,等于上限允许、超过则整份跳过。普通文件和 eval 体分别检查,beast 场景使用解密后的明文长度;超限的包装文件仍可产生符合大小要求的 eval 捕获。负数、非整数、不完整单位及溢出值会报配置错误,本请求不捕获,原有编译流程继续执行;配置错误仍沿用 PHP Warning 处理方式。
过滤发生在 SHA-256、路径锁和源码写入之前,不为被跳过的内容创建历史对象或版本记录,也不更新其锁状态。已有捕获和历史会保留。代码通常已由上游读取,因此此选项限制捕获开销,不限制 PHP 编译或上游解密的内存。
启用 decrypt.log_skips 后,error_log 中会出现以 decrypt: 开头的 JSON 诊断,原因是 excluded 或 max_capture_bytes,附带类型、来源、字节数和上限。预期跳过不触发业务错误处理器,不写入 index.jsonl。来源名中的换行会被转义,source_path_b64 保留原始路径字节。规则和日志去重状态按请求隔离,在 FPM worker 复用时会重置。
CLI 建议将 JSON 数组写入配置文件,并用 php -c /path/to/php.ini 加载;配置好扩展后也可用 -d decrypt.max_capture_bytes=4M 临时覆盖上限。若直接传 JSON,需同时保留 INI 的引号,例如:
php -d "decrypt.exclude_paths='[\"vendor/\",\"storage/cache/\"]'" \
-d decrypt.max_capture_bytes=2M /www/shop/index.php以下路径均相对于 decrypt.save_path,哈希值使用完整 SHA-256:
| 路径 | 内容 |
|---|---|
current/index.php |
入口文件最近捕获到的原始字节 |
current/app/Controller/User.php |
按项目相对目录结构保存的文件 |
eval/<来源哈希>/<内容哈希>.code |
独立代码片段,保留原字节,不补 PHP 标签 |
objects/<内容哈希前两位>/<内容哈希>.code |
开启 history 时保存的去重历史对象 |
index.jsonl |
开启 index 时追加的版本记录 |
locks/<路径哈希>.lock |
并发锁和发布状态,捕获运行期间不要删除 |
current/ 保留文件编译入口收到的内容。对于 Base64 + eval 包装,包装文件在 current/,求值前的代码字符串在 eval/。原项目删除文件后,已有捕获仍会保留。
同一普通文件从 A 变成 B 再变回 A,默认记录三次版本变化,但只保存两个历史内容对象。不同路径的相同内容也共用历史对象。
捕获结果只包含实际经过捕获入口的代码,不包含图片、CSS 或未执行源码,也不构成同一时刻的完整项目快照。索引字段、写入流程及完整性检查见存储与恢复。
仓库提供由两个文件组成的 Base64 + eval 示例及明文对照。从仓库根目录执行,输出路径必须尚不存在:
PHP_BIN="$(command -v php)" \
DECRYPT_EXTENSION="$PWD/modules/decrypt.so" \
examples/run-demo.sh /tmp/php-decrypt-demo脚本会运行包装项目、捕获文件与 eval 体、校验原始字节和历史对象,再导出并执行恢复后的示例。成功时会看到两次 Hello from captured PHP;restored/index.php 和 restored/lib/message.php 与 examples/demo/plain 一致。
示例的导出逻辑针对已知的“一文件一个完整 eval 体”,不能直接用于自动恢复任意项目。脚本会隔离 PHP 配置,并在需要时显式加载共享的 hash、json 依赖。
- OPcache: 缓存命中可能跳过文件编译钩子,不能假设每次请求都重新捕获。快速使用命令和集成测试关闭了对应的 OPcache 功能。
- 扩展组合: 已在 PHP 7.4.33 NTS 的 CLI/FPM 验证 php-beast 2.7 的 DES、AES 和 ASCII BASE64;必须先加载
decrypt.so,再加载beast.so。中文 BASE64 存在上游问题,详见 php-beast 集成验证。其他加密扩展及 OPcache/Xdebug 组合仍需逐一验证。 - 写入与恢复: 源码通过同目录临时文件完整写入后再重命名发布,但多个输出文件之间没有事务保证。异常中断及同步选项的边界见存储与恢复。
Linux 和 macOS 均可用以下脚本在独立目录构建并运行 PHPT;构建目录应使用仓库之外的新空目录,PHP 工具路径需替换为实际值:
PHP_BIN=/path/to/php PHPIZE=/path/to/phpize PHP_CONFIG=/path/to/php-config \
tools/test-linux.sh /tmp/php-decrypt-build脚本会处理共享 hash、json 依赖,确认扩展实际加载,并检查测试确实执行。test-linux.sh 的名称沿用早期版本,同样适用于 macOS。
| 文档或工具 | 用途 |
|---|---|
| 基础测试与性能记录 | PHPT 场景、示例结果及本地微基准 |
| 运行环境验证 | Linux/macOS、Intel、FPM 和 ZTS 的验证矩阵及复现方法 |
| php-beast 集成验证 | PHP 7 的真实扩展加密、加载顺序、明文校验和 FPM 检查 |
| 版本发布 | 通过版本标签自动验证并发布 GitHub 源码 Release |
| tools/test-fpm.py | 真实 FPM 连续请求、异常恢复和多 worker 并发检查 |
| tools/test-zts.php | 配合 parallel 进行原生线程捕获检查 |
| tools/verify-captures.php | 校验索引引用的对象与文件,按索引恢复镜像 |
parallel 仅用于线程测试,不是本扩展的运行依赖。性能数据为本地微基准,不代表业务请求、网络文件系统或长期生产负载。
Apache License 2.0,详见 LICENSE。