PHP 如何利用 Opcache 来实现保护源码

PHP 如何利用 Opcache 来实现保护源码

感兴趣得可以试试看!!!

要求

  • 不用 IonCube(或类似的)。不知道这是啥的话,就是加密 PHP 代码但还能运行的工具。问题是太贵了。😅
  • 性能要好,PHP 原生支持。

原文链接 PHP 如何利用 Opcache 来实现保护源码

后来想到,PHP 有个"opcache"功能,能把源码编译成操作码(机器语言)在 Zend VM 上跑,跟 Java 差不多 :) 厉害的是,这样既保护了代码,又提升了性能!

开始干活。要让这套方案跑起来,得把代码打包成镜像(就是个只读的存储,跟系统其他部分隔离开),因为 opcache 是全局生效的,不管哪个 PHP 项目。最好的工具就是 Docker。(Docker 比虚拟机轻量多了,分发部署都很方便)。

这次用 Laravel 做例子。为啥选它?因为组件多,依赖库也多,能遇到各种坑,学到的东西也多。

一般来说,我们的核心代码都在 /app 目录里,这部分需要保护。其他目录像 /vendor 都是开源库,不用管。

具体步骤

第一步 ,在项目根目录建个 warm-opcache.php 文件。这玩意儿会调用 opcache_compile_file() 手动让 PHP 编译代码。

php 复制代码
<?php
$directory = new RecursiveDirectoryIterator('/var/www/app'); # 我们用 /var/www
$iterator = new RecursiveIteratorIterator($directory);

foreach ($iterator as $file) {
    if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {
        echo "编译中: {$file}\n";
        opcache_compile_file($file);
    }
}

第二步 ,建个 empty-preserve-time.sh 脚本(记得 chmod +x 给执行权限)。这个脚本会把 PHP 文件内容清空,但保留时间戳。为啥要保留时间戳?因为文件修改时间一变,opcache 就会重新加载。

bash 复制代码
#!/bin/bash

for file in $(find ./app -type f -name "*.php"); do
  timestamp=$(stat -c %Y "$file")  # 获取修改时间(从纪元开始的秒数)
  : > "$file"                      # 清空文件
  touch -d "@$timestamp" "$file"   # 恢复原始时间戳
done

第三步 ,把 zz-opcache.ini 配置文件放到 /usr/local/etc/php/conf.d 目录(或者你系统的 conf.d 在哪就放哪)。(记得先装好 PHP 的 opcache 扩展)

ini 复制代码
opcache.enable=1
opcache.enable_cli=1
opcache.validate_timestamps=1
opcache.revalidate_freq=10
opcache.file_cache=/var/www/.opcache
opcache.file_cache_only=1

⚠️ 重要:先把代码 commit 或者备份!下面的操作会删除文件内容!

接下来就是见证奇迹的时刻了。先跑 php warm-opcache.php,再跑 empty-preserve-time.sh,文件内容会被清空,但 /app 目录结构还在,Laravel 项目照样能跑。不信你试试!

这套方法对任何 PHP 项目都管用,不管你用 PSR-4 还是简单的 require()。Laravel 用的是 PSR-4。

不错,概念验证成功。下一步就是打包,要能分发到客户的服务器上。(就像 Go 能编译成 .exe 一样)

直接上 Dockerfile。(这个 Dockerfile 没做层优化,主要是为了好理解)

dockerfile 复制代码
FROM php:8.3-fpm-alpine # 根据需要修改

# 添加更多 pecl install 或 docker-php-ext-install
# 来安装项目需要的扩展

# 启用 opcache
RUN docker-php-ext-install opcache

WORKDIR /var/www
RUN mkdir -p /var/www/.opcache

# 复制源码
COPY app ./app
COPY artisan ./artisan
COPY bootstrap ./bootstrap
COPY database ./database
COPY config ./config
COPY public ./public
COPY resources ./resources
COPY routes ./routes
COPY storage ./storage
COPY composer.* .

# 安装 ini 文件
COPY zz-opcache.ini /usr/local/etc/php/conf.d

# Laravel 的 composer install 需要 .env
# 我们复制一个假的 .env
COPY .env.example .env

# 安装 PHP 依赖(不要把这行移到上面)
RUN composer install --no-dev --optimize-autoloader

# 编译并删除 /app 中的源码
RUN php warm-opcache.php
RUN ./empty-preserve-time.sh

# 恭喜!你的代码已经被清除了!
# 如果不信,你可以 `ls` 你的 /app 目录并 `cat` 它

# 如果需要,你可以创建一个 ENTRYPOINT 脚本,也可以执行
# ./artisan queue:work, 或 ./artisan schedule:work
CMD ["./artisan serve"]

现在,你可以 docker builddocker push 到你的注册服务器,然后从客户的本地服务器 pull,而不用裸露地交付代码!当你有更新时,简单的 docker pull 就能节省很多时间!

可能有人会问,我们能删除 /app 目录而不是留空吗?不行。因为"opcache"会检查文件是否存在。

额外收获!

上面的 Dockerfile 不安全。为什么?因为 Docker 在每个阶段都使用层,意味着当你 COPY app ./app 时,它实际上复制了你未保护的代码,并创建了一个层。Docker 专家可以轻松解开这些层,获取你的原始代码。

解决方案是使用多阶段构建。这是修订后的 Dockerfile。注意我们在第 1 行添加了 as build

dockerfile 复制代码
FROM php:8.3-fpm-alpine as build # 根据需要修改

# 添加更多 pecl install 或 docker-php-ext-install
# 来安装项目需要的扩展

# 启用 opcache
RUN docker-php-ext-install opcache

WORKDIR /var/www
RUN mkdir -p /var/www/.opcache

# 复制源码
COPY app ./app
COPY artisan ./artisan
COPY bootstrap ./bootstrap
COPY database ./database
COPY config ./config
COPY public ./public
COPY resources ./resources
COPY routes ./routes
COPY storage ./storage
COPY composer.* .

# 安装 ini 文件
COPY zz-opcache.ini /usr/local/etc/php/conf.d

# Laravel 的 composer install 需要 .env
# 我们复制一个假的 .env
COPY .env.example .env

# 安装 PHP 依赖(不要把这行移到上面)
RUN composer install --no-dev --optimize-autoloader

# 编译并删除 /app 中的源码
RUN php warm-opcache.php
RUN ./empty-preserve-time.sh

# 恭喜!你的代码已经被清除了!
# 如果不信,你可以 `ls` 你的 /app 目录并 `cat` 它

# ======== 这里是多阶段层构建 ===========
FROM php:8.3-fpm-alpine # 根据需要修改

WORKDIR /var/www

# (重复上面完全相同的步骤)
# 添加更多 pecl install 或 docker-php-ext-install
# 来安装项目需要的扩展

# 启用 opcache
RUN docker-php-ext-install opcache

# 安装 ini 文件
COPY zz-opcache.ini /usr/local/etc/php/conf.d

# 从 `build` 复制清空的文件和 opcache 代码到这里
COPY --from=build /var/www .

# 如果需要,你可以创建一个 ENTRYPOINT 脚本,也可以执行
# ./artisan queue:work, 或 ./artisan schedule:work
CMD ["./artisan serve"]
相关推荐
沐雨橙风ιε2 小时前
Spring Boot整合Apache Shiro权限认证框架(应用篇)
java·spring boot·后端·apache shiro
考虑考虑2 小时前
fastjson调用is方法开头注意
java·后端·java ee
小蒜学长2 小时前
springboot基于javaweb的小零食销售系统的设计与实现(代码+数据库+LW)
java·开发语言·数据库·spring boot·后端
brzhang3 小时前
为什么 OpenAI 不让 LLM 生成 UI?深度解析 OpenAI Apps SDK 背后的新一代交互范式
前端·后端·架构
EnCi Zheng3 小时前
JPA 连接 PostgreSQL 数据库完全指南
java·数据库·spring boot·后端·postgresql
brzhang3 小时前
OpenAI Apps SDK ,一个好的 App,不是让用户知道它该怎么用,而是让用户自然地知道自己在做什么。
前端·后端·架构
LucianaiB3 小时前
从玩具到工业:基于 CodeBuddy code CLI 构建电力变压器绕组短路智能诊断系统
后端
宁小法4 小时前
PHP 数组 如何将新元素加到数组第一个位置(支持指定key => value)
php·数组·首个元素
武子康4 小时前
大数据-118 - Flink 批处理 DataSet API 全面解析:应用场景、代码示例与优化机制
大数据·后端·flink
不要再敲了5 小时前
Spring Security 完整使用指南
java·后端·spring