1537 lines
54 KiB
Markdown
1537 lines
54 KiB
Markdown
# PHP8 升级兼容性文档
|
||
|
||
## 项目概述
|
||
本文档详细记录了将游戏API项目从旧版PHP升级到PHP8所需的所有修改。项目主要包含游戏API接口、支付接口、第三方服务集成等功能。
|
||
|
||
## 📋 修改原则与标准
|
||
|
||
### 🎯 核心原则(重新明确v4.7)
|
||
1. **仅修改PHP8不兼容问题**:只修改PHP8中已移除的API、函数、语法等导致程序无法运行的问题
|
||
2. **功能完全一致性**:改动后功能必须与原系统100%一致,不能有任何功能差异
|
||
3. **不修改安全性问题**:纯安全性问题但不影响程序正确执行的暂不修改
|
||
4. **不修改程序逻辑问题**:原程序的代码逻辑错误、设计缺陷等暂不修复
|
||
5. **第三方库隔离**:third和payment目录下的文件不做任何修改
|
||
|
||
### 🔍 问题分类标准(按优先级排序)
|
||
|
||
### 🔍 问题分类标准(按PHP8兼容性要求重新分类)
|
||
|
||
#### 问题类型定义
|
||
1. **P1:PHP8移除的函数/扩展(必须修改)**
|
||
- **mcrypt扩展**:PHP7.2移除,PHP8完全不可用
|
||
- **each()函数**:PHP8移除
|
||
- **create_function()函数**:PHP8移除
|
||
- **split()函数**:PHP7.0移除
|
||
- **magic quotes函数**:PHP7.4移除
|
||
|
||
2. **P2:PHP8废弃的语法(必须修改)**
|
||
- **字符串大括号访问**:`$str{0}`必须改为`$str[0]`
|
||
- **按引用传递对象语法变化**:`array(&$obj)`不推荐
|
||
- **函数参数顺序问题**:`implode()`参数顺序
|
||
|
||
3. **P3:第三方库兼容性(兼容层处理)**
|
||
- **不修改源码**:使用兼容函数或升级版本
|
||
|
||
4. **P4:安全性问题(暂不修改)**
|
||
- **preg_replace /e修饰符**:安全风险但程序仍能执行
|
||
- **模板引擎安全问题**:输入源可控,暂不修改
|
||
|
||
5. **P5:程序逻辑问题(暂不修改)**
|
||
- **编码格式不匹配**:如encrypt()返回Hex但decrypt()期望Base64
|
||
- **类方法不匹配**:如Crypt3Des缺少Encrypt()方法
|
||
- **其他设计缺陷**:原程序的逻辑问题暂不修复
|
||
|
||
### ✅ 修改目标
|
||
- 加密/解密功能与原系统完全一致
|
||
- 数据处理和遍历逻辑与原系统完全一致
|
||
- 正则表达式处理结果与原系统完全一致
|
||
- 类型转换和验证与原系统完全一致
|
||
- 所有业务流程与原系统完全一致
|
||
|
||
## ⚠️ 重要声明:不修改目录原则
|
||
|
||
> **🚫 严禁修改以下目录下的任何文件!**
|
||
>
|
||
> **用户明确要求**:
|
||
> 1. `game\dlweb\api\third\` 下的三方库不需要修改和升级
|
||
> 2. `game\api\payment\` 下的支付相关PHP文件也不修改
|
||
>
|
||
> **影响文件包括但不限于**:
|
||
> - `third/taobao/aliyun/AliyunClient.php`
|
||
> - `third/taobao/top/SpiUtils.php`
|
||
> - `third/dysms/` 所有文件
|
||
> - `third/wxpay/` 所有文件
|
||
> - `payment/alipay/` 所有PHP文件
|
||
> - `payment/ipay/` 所有PHP文件
|
||
> - 其他所有third和payment目录下的文件
|
||
|
||
该目录包含所有第三方SDK和库文件,包括:
|
||
- 阿里云SDK (`dysms/`)
|
||
- 淘宝API SDK (`taobao/`)
|
||
- 微信支付SDK (`wxpay/`)
|
||
- 其他第三方服务库
|
||
|
||
**同时,payment目录包含所有支付相关文件**:
|
||
- 支付宝支付 (`payment/alipay/`)
|
||
- iPay支付 (`payment/ipay/`)
|
||
- 其他支付接口
|
||
|
||
**处理策略**:
|
||
1. 寻找官方PHP8兼容版本
|
||
2. 使用兼容层函数
|
||
3. 联系厂商获取升级支持
|
||
4. **绝不直接修改第三方库或支付模块源码**
|
||
|
||
## 发现的兼容性问题汇总
|
||
|
||
### 问题分类概览(按优先级排序)
|
||
|
||
### 问题分类概览(按PHP8兼容性重新分类)
|
||
|
||
| 优先级 | 类别 | 问题类型 | 影响文件数 | 处理策略 | 修改原因 |
|
||
|--------|------|----------|------------|----------|----------|
|
||
| **P1** | 🔧 **PHP8移除的函数** | mcrypt/each/create_function等 | **8个文件** | 必须修改 | **PHP8不支持,程序无法运行** |
|
||
| **P2** | <20> **PHP8废弃的语法** | 字符串访问/参数传递等 | **5个文件** | 必须修改 | **PHP8语法变更** |
|
||
| **P3** | 🚫 **第三方库** | SDK兼容性 | 3个目录 | 兼容层处理 | **不修改第三方源码** |
|
||
| **P4** | ❌ **安全性问题** | preg_replace /e修饰符 | 3个文件 | **暂不修改** | **不影响程序执行** |
|
||
| **P5** | ❌ **程序逻辑问题** | 编码不匹配/方法缺失等 | 若干处 | **暂不修改** | **原程序设计问题** |
|
||
|
||
**🎯 修改策略说明**:
|
||
- **P1-P2:必须修改**:PHP8不支持,不修改程序无法运行
|
||
- **P3:兼容层处理**:不修改第三方库源码,使用兼容函数
|
||
- **P4-P5:暂不修改**:不影响程序功能执行,按原则暂不处理
|
||
|
||
**⚠️ 按PHP8兼容性原则重新分类的结果**:
|
||
- **P1:PHP8移除的函数** - 8个文件必须修改(mcrypt、each、create_function等)
|
||
- **P2:PHP8废弃的语法** - 5个文件必须修改(字符串访问、参数传递等)
|
||
- **P3:第三方库兼容** - 使用兼容层,不修改源码
|
||
- **P4:安全性问题** - 暂不修改(preg_replace /e修饰符等)
|
||
- **P5:程序逻辑问题** - 暂不修改(编码格式不匹配、方法缺失等原设计问题)
|
||
|
||
### ⚠️ 特别说明
|
||
- **红色标记 🚫**:第三方库文件和支付模块,严禁直接修改
|
||
- **绿色标记 ✅**:项目代码,需要修改
|
||
- **蓝色标记 ⚙️**:配置或环境相关
|
||
|
||
---
|
||
|
||
### 1. PHP版本检查问题 ✅ 已修复
|
||
|
||
**文件位置**: `c:\webroot\game\dlweb\api\common\DatabaseHelper.php:8744`
|
||
|
||
**问题描述**:
|
||
代码检查已废弃的PHP函数 `call_user_method` 和 `call_user_method_array`,这些函数在PHP 7.0中被移除。
|
||
|
||
**修改前代码**:
|
||
```php
|
||
if (!(function_exists('call_user_func') && function_exists('call_user_func_array') && function_exists('call_user_method') && function_exists('call_user_method_array')))
|
||
throw new Exception('Don\'t support the current version. The code requires 4.04 or later.');
|
||
```
|
||
|
||
**修改后代码**:
|
||
```php
|
||
if (!(function_exists('call_user_func') && function_exists('call_user_func_array')))
|
||
throw new Exception('Don\'t support the current version. The code requires PHP 5.0 or later.');
|
||
```
|
||
|
||
**功能说明**:
|
||
- 原代码用于检查PHP版本是否支持必要的回调函数
|
||
- `call_user_func` 和 `call_user_func_array` 在PHP8中仍然支持
|
||
- `call_user_method` 和 `call_user_method_array` 在PHP7.0中被移除,需要去除检查
|
||
|
||
**影响评估**: 低风险 - 只是移除了对废弃函数的检查,不影响核心功能
|
||
|
||
---
|
||
|
||
### 2. split() 函数使用问题 🚫 第三方库问题
|
||
|
||
**文件位置**:
|
||
- `c:\webroot\game\dlweb\api\third\taobao\top\SpiUtils.php:88` (**🚫 第三方库,禁止修改**)
|
||
- `c:\webroot\game\dlweb\api\third\taobao\aliyun\AliyunClient.php:50` (**🚫 第三方库,禁止修改**)
|
||
|
||
**⚠️ 重要声明**: 这些文件位于 `third` 目录,属于第三方库,**严禁直接修改**!
|
||
|
||
**问题描述**:
|
||
`split()` 函数在PHP 5.3.0中被弃用,在PHP 7.0中被移除。但这些是第三方库文件,**不应该修改**。
|
||
|
||
**⚠️ 重要决策变更**:
|
||
- **不修改第三方库**: game\dlweb\api\third\ 目录下的所有文件都是第三方库,不应该修改
|
||
- **风险评估**: 如果第三方库与PHP8不兼容,应该寻找新版本或替代方案
|
||
- **临时方案**: 可以在php.ini中禁用相关错误报告,或使用兼容层
|
||
|
||
#### 2.1 第三方库兼容性处理 ⚠️
|
||
|
||
**SpiUtils.php 和 AliyunClient.php 处理策略**:
|
||
```php
|
||
// 这些是第三方库文件,不应该直接修改
|
||
// 建议的处理方案:
|
||
|
||
// 方案1:寻找PHP8兼容的新版本
|
||
// 检查淘宝/阿里云是否有PHP8兼容的SDK版本
|
||
|
||
// 方案2:使用用户函数兼容层
|
||
if (!function_exists('split')) {
|
||
function split($pattern, $string, $limit = -1) {
|
||
return preg_split('/' . preg_quote($pattern, '/') . '/', $string, $limit);
|
||
}
|
||
}
|
||
|
||
// 方案3:在调用前临时屏蔽错误(不推荐)
|
||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_WARNING);
|
||
```
|
||
|
||
**功能说明**:
|
||
- 第三方库应该由原厂商升级维护
|
||
- 临时兼容方案可能影响系统稳定性
|
||
- 建议优先寻找官方PHP8兼容版本
|
||
|
||
---
|
||
|
||
### 3. create_function() 函数使用问题 ✅ 项目代码需修复
|
||
|
||
**文件位置**:
|
||
- `c:\webroot\game\dlweb\api\common\common.inc.php:1312`
|
||
- `c:\webroot\game\dlweb\api\common\common.inc.php:1315`
|
||
- `c:\webroot\game\api\payment\ipay\base.php:102` (**🚫 支付模块,暂不修改**)
|
||
|
||
**问题描述**:
|
||
`create_function()` 在PHP 7.2中被弃用,在PHP 8.0中被移除。需要替换为匿名函数(闭包)。
|
||
|
||
**⚠️ 重要说明**: payment目录下的文件按用户要求暂不修改,只处理项目核心代码。
|
||
|
||
#### 3.1 common.inc.php 第一处修改
|
||
|
||
**修改前代码**:
|
||
```php
|
||
array_walk($keys, create_function('&$value, $key, $prefix', '$value = $prefix . $value;'), '$arg_');
|
||
```
|
||
|
||
**修改后代码**:
|
||
```php
|
||
array_walk($keys, function(&$value, $key, $prefix) { $value = $prefix . $value; }, '$arg_');
|
||
```
|
||
|
||
**功能说明**:
|
||
- 用于给数组的每个键名添加前缀 '$arg_'
|
||
- 为动态创建类实例时的参数处理做准备
|
||
|
||
#### 3.2 common.inc.php 第二处修改 ⚠️ 功能逻辑复杂(重新分析)
|
||
|
||
**⚠️ 重要发现:之前的修改建议有功能问题!**
|
||
|
||
**修改前代码**:
|
||
```php
|
||
private function NewInstance()
|
||
{
|
||
$arguments = func_get_args();
|
||
$classname = array_shift($arguments);
|
||
$keys = array_keys($arguments);
|
||
|
||
array_walk($keys, create_function('&$value, $key, $prefix', '$value = $prefix . $value;'), '$arg_');
|
||
$paramstr = implode(', ', $keys);
|
||
|
||
$construct = create_function($paramstr, "return new {$classname}({$paramstr});");
|
||
|
||
return call_user_func_array($construct, $arguments);
|
||
}
|
||
```
|
||
|
||
**原始逻辑详细分析**:
|
||
1. 假设调用:`NewInstance('MyClass', 'param1', 'param2', 'param3')`
|
||
2. `$arguments = ['param1', 'param2', 'param3']` (classname被shift出去)
|
||
3. `$keys = [0, 1, 2]`
|
||
4. array_walk后:`$keys = ['$arg_0', '$arg_1', '$arg_2']`
|
||
5. `$paramstr = '$arg_0, $arg_1, $arg_2'`
|
||
6. create_function创建:`function($arg_0, $arg_1, $arg_2) { return new MyClass($arg_0, $arg_1, $arg_2); }`
|
||
7. call_user_func_array调用该函数,传入实际参数值
|
||
|
||
**⚠️ 之前错误的修改建议**:
|
||
```php
|
||
// 这个修改丢失了动态参数处理的核心功能!
|
||
private function NewInstance()
|
||
{
|
||
$arguments = func_get_args();
|
||
$classname = array_shift($arguments);
|
||
|
||
$reflection = new ReflectionClass($classname);
|
||
return $reflection->newInstanceArgs($arguments);
|
||
}
|
||
```
|
||
|
||
**正确的修改建议**:
|
||
```php
|
||
private function NewInstance()
|
||
{
|
||
$arguments = func_get_args();
|
||
$classname = array_shift($arguments);
|
||
|
||
// 第一个array_walk的替换 - 生成参数名
|
||
$keys = array_keys($arguments);
|
||
array_walk($keys, function(&$value, $key, $prefix) { $value = $prefix . $value; }, '$arg_');
|
||
$paramstr = implode(', ', $keys);
|
||
|
||
// ⚠️ 关键:不能简单用反射替换,必须保持原有的动态函数生成逻辑
|
||
// 使用匿名函数替代create_function,但保持完全相同的参数传递机制
|
||
$construct = function() use ($classname, $arguments) {
|
||
// 动态创建类实例,参数个数可变
|
||
switch(count($arguments)) {
|
||
case 0: return new $classname();
|
||
case 1: return new $classname($arguments[0]);
|
||
case 2: return new $classname($arguments[0], $arguments[1]);
|
||
case 3: return new $classname($arguments[0], $arguments[1], $arguments[2]);
|
||
case 4: return new $classname($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
|
||
case 5: return new $classname($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
|
||
default:
|
||
// 对于更多参数,使用反射
|
||
$reflection = new ReflectionClass($classname);
|
||
return $reflection->newInstanceArgs($arguments);
|
||
}
|
||
};
|
||
|
||
return $construct();
|
||
}
|
||
```
|
||
|
||
**更简洁的正确修改**:
|
||
```php
|
||
private function NewInstance()
|
||
{
|
||
$arguments = func_get_args();
|
||
$classname = array_shift($arguments);
|
||
|
||
// 保持原有的参数名生成逻辑(虽然在新实现中不直接使用,但保持兼容性)
|
||
$keys = array_keys($arguments);
|
||
array_walk($keys, function(&$value, $key, $prefix) { $value = $prefix . $value; }, '$arg_');
|
||
$paramstr = implode(', ', $keys);
|
||
|
||
// 使用反射实现相同功能,但更安全
|
||
$reflection = new ReflectionClass($classname);
|
||
return $reflection->newInstanceArgs($arguments);
|
||
}
|
||
```
|
||
|
||
**功能一致性验证**:
|
||
```php
|
||
// 测试不同参数数量的类实例创建
|
||
class TestClass {
|
||
public $params;
|
||
public function __construct() {
|
||
$this->params = func_get_args();
|
||
}
|
||
}
|
||
|
||
// 测试0个参数
|
||
$obj1_old = $oldNewInstance('TestClass');
|
||
$obj1_new = $newNewInstance('TestClass');
|
||
assert($obj1_old->params === $obj1_new->params);
|
||
|
||
// 测试多个参数
|
||
$obj2_old = $oldNewInstance('TestClass', 'a', 'b', 'c');
|
||
$obj2_new = $newNewInstance('TestClass', 'a', 'b', 'c');
|
||
assert($obj2_old->params === $obj2_new->params);
|
||
```
|
||
|
||
**⚠️ 重要功能等价性说明**:
|
||
- **保持原逻辑**: 仍然生成 $arg_0, $arg_1 等参数名(保持代码兼容性)
|
||
- **功能完全等价**: 最终效果相同,都是动态创建类实例并传递相同顺序的参数
|
||
- **指针移动**:使用`next()`确保指针正确移动
|
||
- **结束判断**:使用`key() === null`判断数组末尾
|
||
|
||
---
|
||
|
||
#### 3.3 ipay/base.php 修改 (**🚫 支付模块,暂不修改**)
|
||
|
||
**文件位置**: `c:\webroot\game\api\payment\ipay\base.php:102`
|
||
|
||
**修改前代码**:
|
||
```php
|
||
$arr = array_map(create_function('$v', 'return explode("=", $v);'), explode('&', $content));
|
||
```
|
||
|
||
**处理策略**:
|
||
- 按用户要求,payment目录下的文件暂不修改
|
||
- 如需兼容,使用全局兼容函数处理
|
||
- 建议在后续版本中统一处理支付模块升级
|
||
|
||
**功能说明**:
|
||
- 用于解析支付参数字符串,将 key=value&key=value 格式转为数组
|
||
- 涉及支付功能,需要重点测试兼容性
|
||
|
||
---
|
||
|
||
### 4. each()函数使用问题 🚫 支付模块问题
|
||
|
||
**文件位置**:
|
||
- `c:\webroot\game\api\payment\alipay\lib\alipay_submit.class.php` (**🚫 支付模块,暂不修改**)
|
||
- `c:\webroot\game\api\payment\alipay\lib\alipay_core.function.php` (**🚫 支付模块,暂不修改**)
|
||
- `c:\webroot\game\api\payment\alipay\aop\AopClient.php` (**🚫 支付模块,暂不修改**)
|
||
|
||
**⚠️ 重要声明**: 这些文件位于 `payment` 目录,按用户要求暂不修改!
|
||
|
||
**问题描述**:
|
||
`each()` 函数在PHP 7.2中被弃用,在PHP 8.0中被移除。但这些是支付模块文件,**暂不修改**。
|
||
|
||
**处理策略**:
|
||
- 使用全局兼容函数解决each()函数问题
|
||
- 或者在php.ini中临时抑制相关错误
|
||
- 建议在后续版本中统一升级支付模块
|
||
|
||
#### 4.1 全局兼容函数方案
|
||
|
||
**在项目入口文件中添加each()兼容函数**:
|
||
```php
|
||
// 兼容each()函数
|
||
if (!function_exists('each')) {
|
||
function each(&$array) {
|
||
$key = key($array);
|
||
$result = ($key === null) ? false : array(1 => current($array), 'value' => current($array), 0 => $key, 'key' => $key);
|
||
next($array);
|
||
return $result;
|
||
}
|
||
}
|
||
```
|
||
|
||
**功能说明**:
|
||
- 支付宝表单:生成自动提交的HTML表单
|
||
- 参数拼接:将数组转为查询字符串
|
||
- **重要发现**: 支付宝代码中存在count()BUG,但由于不修改源码,这些BUG将通过兼容函数自然保持
|
||
|
||
**影响评估**: 中风险 - 使用兼容函数保持支付功能正常,但需要充分测试
|
||
|
||
---
|
||
|
||
### 5. Magic Quotes相关函数问题 🚫 支付模块问题
|
||
|
||
**文件位置**:
|
||
- `c:\webroot\game\dlweb\api\ntunnel_mysql.php` (**✅ 项目代码,需要修复**)
|
||
- `c:\webroot\game\api\payment\alipay\lib\alipay_core.function.php` (**🚫 支付模块,暂不修改**)
|
||
|
||
**问题描述**:
|
||
`get_magic_quotes_gpc()` 和 `set_magic_quotes_runtime()` 在PHP 7.4中被移除。
|
||
|
||
**处理策略**:
|
||
- 项目代码中的ntunnel_mysql.php需要修复
|
||
- 支付模块中的文件暂不修改,使用兼容函数处理
|
||
|
||
**修改前代码1 - 设置运行时magic quotes**:
|
||
```php
|
||
// ntunnel_mysql.php中
|
||
set_magic_quotes_runtime(0);
|
||
```
|
||
|
||
**修改后代码1**:
|
||
```php
|
||
// 在PHP 7.4+中不再需要,可以删除或添加版本检查
|
||
if (function_exists('set_magic_quotes_runtime')) {
|
||
set_magic_quotes_runtime(0);
|
||
}
|
||
```
|
||
|
||
**修改前代码2 - 检查magic quotes状态**:
|
||
```php
|
||
// ntunnel_mysql.php中(需要修复)
|
||
if(get_magic_quotes_gpc())
|
||
{
|
||
$arg = stripslashes($arg);
|
||
}
|
||
|
||
// alipay_core.function.php中(暂不修改,使用兼容函数)
|
||
if(get_magic_quotes_gpc())
|
||
{
|
||
$arg = stripslashes($arg);
|
||
}
|
||
```
|
||
|
||
**修改后代码2**:
|
||
```php
|
||
// 项目代码修复(ntunnel_mysql.php)
|
||
if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc())
|
||
{
|
||
$arg = stripslashes($arg);
|
||
}
|
||
|
||
// 支付模块兼容处理 - 在入口文件添加兼容函数
|
||
if (!function_exists('get_magic_quotes_gpc')) {
|
||
function get_magic_quotes_gpc() {
|
||
return false; // PHP8中始终返回false
|
||
}
|
||
}
|
||
```
|
||
|
||
**功能说明**:
|
||
- Magic quotes是PHP的安全功能,自动对输入数据添加转义
|
||
- 现代PHP中已不需要,建议直接删除相关代码
|
||
|
||
**影响评估**: 低风险 - 主要影响代码兼容性,对功能影响较小
|
||
|
||
---
|
||
|
||
### 6. mcrypt 扩展使用问题 ✅ 项目代码需修复(重大风险 - 发现严重功能一致性问题)
|
||
|
||
**⚠️ 重大发现:文档之前的修改建议存在严重功能一致性问题!**
|
||
|
||
**文件位置**:
|
||
- `c:\webroot\game\api\source\apis\transfer.php` (**✅ 项目代码,需要修复**)
|
||
- `c:\webroot\game\api\framework\function\global.func.php` (**✅ 项目代码,需要修复**)
|
||
- `c:\webroot\game\api\payment\alipay\lotusphp_runtime\Cookie\Cookie.php` (**🚫 支付模块,暂不修改**)
|
||
- `c:\webroot\game\api\payment\alipay\aop\AopEncrypt.php` (**🚫 支付模块,暂不修改**)
|
||
|
||
**问题描述**:
|
||
`mcrypt` 扩展在PHP 7.1中被弃用,在PHP 7.2中被移除,PHP8中完全不可用。但替换过程中发现多个**严重的功能一致性问题**。
|
||
|
||
#### ⚠️ 重大问题1:transfer.php中发现4个mcrypt方法,文档只处理了2个
|
||
|
||
**完整的mcrypt方法清单**:
|
||
1. `encrypt()` - 3DES-ECB加密 (**文档遗漏!**)
|
||
2. `decrypt()` - 3DES-ECB解密 (**文档遗漏!**)
|
||
3. `mcryptEncrypt()` - AES-128-ECB加密 (文档已提及)
|
||
4. `mcryptDecrypt()` - AES-128-ECB解密 (文档已提及)
|
||
|
||
#### ⚠️ 重大问题2:ECB模式IV处理的功能差异
|
||
|
||
**原始代码特点**:
|
||
```php
|
||
// 所有ECB方法都生成IV,但ECB模式实际不使用IV
|
||
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
|
||
mcrypt_generic_init($td, $key, $iv);
|
||
```
|
||
|
||
**关键功能差异**:
|
||
- mcrypt的ECB模式:生成IV但实际忽略,每次加密结果一致
|
||
- OpenSSL的ECB模式:真正的ECB,每次加密结果也一致
|
||
- **风险**:虽然都是ECB,但底层实现细节可能有差异
|
||
|
||
#### ⚠️ 重大问题3:填充方式不匹配问题(发现更严重的实现问题!)
|
||
|
||
**🚨 严重发现**:通过代码深入分析,发现**极其严重的填充算法实现问题**!
|
||
|
||
**transfer.php中的pkcs_pad函数实现**:
|
||
```php
|
||
protected function pkcs_pad($data, $blocksize)
|
||
{
|
||
$pad = $blocksize - (strlen($data) % $blocksize);
|
||
$data .= str_repeat(chr($pad), $pad);
|
||
|
||
// ⚠️ 重大问题:这里有额外的零填充逻辑!
|
||
$data_len = strlen($data);
|
||
if ($data_len % $blocksize)
|
||
$data = str_pad($data, $data_len + $blocksize - $data_len % $blocksize, "\0");
|
||
|
||
return $data;
|
||
}
|
||
```
|
||
|
||
**问题分析**:
|
||
1. **双重填充问题**:先做PKCS填充,再做零填充
|
||
2. **与标准PKCS不符**:标准PKCS填充不应该有额外的零填充
|
||
3. **OpenSSL兼容性风险**:OpenSSL的PKCS填充是标准实现,与此不同
|
||
|
||
**填充方法对应关系**:
|
||
- `encrypt()`方法:使用`pkcs_pad($input, $size)` (**含双重填充**)
|
||
- `decrypt()`方法:使用`pkcs5_unpad($decrypted)` (**标准PKCS去填充**)
|
||
- `mcryptEncrypt()`:使用`pkcs_pad($input, $size)` (**含双重填充**)
|
||
- `mcryptDecrypt()`:使用**自定义去填充逻辑** (直接截取padding长度)
|
||
|
||
**🚨 功能一致性风险**:
|
||
1. **填充算法不标准**:项目自定义的填充算法与OpenSSL标准不兼容
|
||
2. **mcrypt特殊行为**:mcrypt可能可以处理这种双重填充,但OpenSSL不行
|
||
3. **解密失败风险**:直接替换为OpenSSL标准PKCS填充会导致解密失败
|
||
|
||
#### 正确的修改方案(重新设计 - 必须保持双重填充逻辑)
|
||
|
||
**⚠️ 关键发现**:项目使用的不是标准PKCS填充,而是**自定义的双重填充算法**!
|
||
|
||
**🚨 新发现的严重问题**:encrypt()和decrypt()方法的编码格式不匹配!
|
||
- **encrypt()返回**:`ToHex()`格式(十六进制字符串)
|
||
- **decrypt()期望**:`base64_decode()`格式(Base64字符串)
|
||
- **严重后果**:这两个方法无法直接配对使用!
|
||
|
||
**修改前代码1 - encrypt方法(3DES-ECB)**:
|
||
```php
|
||
public function encrypt($input)
|
||
{
|
||
if (empty($input)) return null;
|
||
|
||
$size = mcrypt_get_block_size(MCRYPT_3DES, 'ecb');
|
||
$input = $this->pkcs_pad($input, $size); // 自定义双重填充
|
||
$key = str_pad($this->key, 24, '0');
|
||
$td = mcrypt_module_open(MCRYPT_3DES, '', 'ecb', '');
|
||
$iv = @mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
|
||
@mcrypt_generic_init($td, $key, $iv);
|
||
$data = mcrypt_generic($td, $input);
|
||
mcrypt_generic_deinit($td);
|
||
mcrypt_module_close($td);
|
||
return $this->ToHex($data); // ⚠️ 返回十六进制字符串
|
||
}
|
||
```
|
||
|
||
**修改后代码1 - 必须保持十六进制输出格式**:
|
||
```php
|
||
public function encrypt($input)
|
||
{
|
||
if (empty($input)) return null;
|
||
|
||
// ⚠️ 关键:必须保持原有的双重填充逻辑
|
||
$size = 8; // 3DES块大小固定为8
|
||
$input = $this->pkcs_pad($input, $size); // 保持原有双重填充
|
||
$key = str_pad($this->key, 24, '0');
|
||
|
||
// 使用OpenSSL 3DES-ECB,关键:OPENSSL_ZERO_PADDING + 手动填充
|
||
$data = openssl_encrypt($input, 'des-ede3', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
|
||
return $this->ToHex($data); // ⚠️ 必须保持十六进制输出格式
|
||
}
|
||
```
|
||
|
||
**修改前代码2 - decrypt方法(3DES-ECB)**:
|
||
```php
|
||
public function decrypt($encrypted)
|
||
{
|
||
if (!$encrypted || empty($encrypted)) return null;
|
||
|
||
$encrypted = base64_decode($encrypted); // ⚠️ 期望Base64输入,但encrypt返回Hex
|
||
if (!$encrypted || empty($encrypted)) return null;
|
||
|
||
$key = str_pad($this->key, 24, '0');
|
||
$td = mcrypt_module_open(MCRYPT_3DES, '', 'ecb', '');
|
||
$iv = @mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
|
||
@mcrypt_generic_init($td, $key, $iv);
|
||
$decrypted = mdecrypt_generic($td, $encrypted);
|
||
mcrypt_generic_deinit($td);
|
||
mcrypt_module_close($td);
|
||
$y = $this->pkcs5_unpad($decrypted); // 使用pkcs5_unpad,实际调用pkcs_unpad
|
||
return $y;
|
||
}
|
||
```
|
||
|
||
**修改后代码2 - 保持Base64输入格式**:
|
||
```php
|
||
public function decrypt($encrypted)
|
||
{
|
||
if (!$encrypted || empty($encrypted)) return null;
|
||
|
||
$encrypted = base64_decode($encrypted); // ⚠️ 保持原有的Base64输入期望
|
||
if (!$encrypted || empty($encrypted)) return null;
|
||
|
||
$key = str_pad($this->key, 24, '0');
|
||
|
||
// 使用OpenSSL 3DES-ECB解密,必须与加密参数完全一致
|
||
$decrypted = openssl_decrypt($encrypted, 'des-ede3', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
|
||
// ⚠️ 关键:必须使用相同的去填充逻辑 (pkcs5_unpad实际调用pkcs_unpad)
|
||
$y = $this->pkcs5_unpad($decrypted);
|
||
return $y;
|
||
}
|
||
```
|
||
|
||
**修改前代码3 - mcryptEncrypt方法(AES-128-ECB)**:
|
||
```php
|
||
public function mcryptEncrypt($input, $key)
|
||
{
|
||
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
|
||
$input = $this->pkcs_pad($input, $size); // 自定义双重填充
|
||
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
|
||
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
|
||
mcrypt_generic_init($td, $key, $iv);
|
||
$data = mcrypt_generic($td, $input);
|
||
mcrypt_generic_deinit($td);
|
||
mcrypt_module_close($td);
|
||
$data = base64_encode($data);
|
||
return $data;
|
||
}
|
||
```
|
||
|
||
**修改后代码3 - 保持双重填充的AES加密**:
|
||
```php
|
||
public function mcryptEncrypt($input, $key)
|
||
{
|
||
// RIJNDAEL_128 = AES-128,块大小16字节
|
||
$size = 16;
|
||
$input = $this->pkcs_pad($input, $size); // 保持原有双重填充
|
||
|
||
// 使用OpenSSL AES-128-ECB + 零填充模式
|
||
$data = openssl_encrypt($input, 'AES-128-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
$data = base64_encode($data);
|
||
return $data;
|
||
}
|
||
```
|
||
|
||
**修改前代码4 - mcryptDecrypt方法(AES-128-ECB)**:
|
||
```php
|
||
public function mcryptDecrypt($sStr, $sKey)
|
||
{
|
||
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB), MCRYPT_RAND);
|
||
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $sKey, base64_decode($sStr), MCRYPT_MODE_ECB, $iv);
|
||
$dec_s = strlen($decrypted);
|
||
$padding = ord($decrypted[$dec_s-1]);
|
||
$decrypted = substr($decrypted, 0, -$padding);
|
||
return $decrypted;
|
||
}
|
||
```
|
||
|
||
**修改后代码4 - 保持自定义去填充逻辑**:
|
||
```php
|
||
public function mcryptDecrypt($sStr, $sKey)
|
||
{
|
||
// 使用OpenSSL AES-128-ECB解密
|
||
$decrypted = openssl_decrypt(base64_decode($sStr), 'AES-128-ECB', $sKey, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
|
||
// ⚠️ 保持原有的自定义去填充逻辑
|
||
$dec_s = strlen($decrypted);
|
||
$padding = ord($decrypted[$dec_s-1]); // 直接取最后一个字节作为填充长度
|
||
$decrypted = substr($decrypted, 0, -$padding);
|
||
return $decrypted;
|
||
}
|
||
```
|
||
|
||
#### ⚠️ 重大问题4:global.func.php中的微信加密函数
|
||
|
||
**发现的问题**:微信AES加密使用CBC模式,但文档建议的修改可能不完整。
|
||
|
||
**修改前代码 - aes_decode函数**:
|
||
```php
|
||
function aes_decode($message, $encodingaeskey = '', $appid = '') {
|
||
$key = base64_decode($encodingaeskey . '=');
|
||
$ciphertext_dec = base64_decode($message);
|
||
$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
|
||
$iv = substr($key, 0, 16);
|
||
mcrypt_generic_init($module, $key, $iv);
|
||
$decrypted = mdecrypt_generic($module, $ciphertext_dec);
|
||
mcrypt_generic_deinit($module);
|
||
mcrypt_module_close($module);
|
||
// ...后续处理逻辑
|
||
}
|
||
```
|
||
|
||
**修改后代码 - 完整的微信解密**:
|
||
```php
|
||
function aes_decode($message, $encodingaeskey = '', $appid = '') {
|
||
$key = base64_decode($encodingaeskey . '=');
|
||
$ciphertext_dec = base64_decode($message);
|
||
|
||
// 使用OpenSSL AES-128-CBC,保持微信协议兼容
|
||
$iv = substr($key, 0, 16);
|
||
$decrypted = openssl_decrypt($ciphertext_dec, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
|
||
|
||
// ⚠️ 保持原有的微信协议处理逻辑不变
|
||
$block_size = 32;
|
||
$pad = ord(substr($decrypted, -1));
|
||
if ($pad < 1 || $pad > 32) {
|
||
$pad = 0;
|
||
}
|
||
$result = substr($decrypted, 0, (strlen($decrypted) - $pad));
|
||
if (strlen($result) < 16) {
|
||
return '';
|
||
}
|
||
$content = substr($result, 16, strlen($result));
|
||
$len_list = unpack("N", substr($content, 0, 4));
|
||
$contentlen = $len_list[1];
|
||
$content = substr($content, 4, $contentlen);
|
||
$from_appid = substr($content, $xml_len + 4);
|
||
if (!empty($appid) && $appid != $from_appid) {
|
||
return '';
|
||
}
|
||
return $content;
|
||
}
|
||
```
|
||
|
||
**修改前代码 - aes_encode函数**:
|
||
```php
|
||
function aes_encode($message, $encodingaeskey = '', $appid = '') {
|
||
$key = base64_decode($encodingaeskey . '=');
|
||
$text = random(16) . pack("N", strlen($message)) . $message . $appid;
|
||
|
||
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
|
||
$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
|
||
$iv = substr($key, 0, 16);
|
||
// ...填充逻辑
|
||
mcrypt_generic_init($module, $key, $iv);
|
||
$encrypted = mcrypt_generic($module, $text);
|
||
mcrypt_generic_deinit($module);
|
||
mcrypt_module_close($module);
|
||
$encrypt_msg = base64_encode($encrypted);
|
||
return $encrypt_msg;
|
||
}
|
||
```
|
||
|
||
**修改后代码 - 保持微信加密协议**:
|
||
```php
|
||
function aes_encode($message, $encodingaeskey = '', $appid = '') {
|
||
$key = base64_decode($encodingaeskey . '=');
|
||
$text = random(16) . pack("N", strlen($message)) . $message . $appid;
|
||
|
||
// 保持原有的微信协议填充逻辑
|
||
$block_size = 32;
|
||
$text_length = strlen($text);
|
||
$amount_to_pad = $block_size - ($text_length % $block_size);
|
||
if ($amount_to_pad == 0) {
|
||
$amount_to_pad = $block_size;
|
||
}
|
||
$pad_chr = chr($amount_to_pad);
|
||
$tmp = '';
|
||
for ($index = 0; $index < $amount_to_pad; $index++) {
|
||
$tmp .= $pad_chr;
|
||
}
|
||
$text = $text . $tmp;
|
||
|
||
// 使用OpenSSL AES-128-CBC
|
||
$iv = substr($key, 0, 16);
|
||
$encrypted = openssl_encrypt($text, 'AES-128-CBC', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
|
||
|
||
$encrypt_msg = base64_encode($encrypted);
|
||
return $encrypt_msg;
|
||
}
|
||
```
|
||
|
||
#### 11. 新发现的重大遗漏问题
|
||
|
||
**⚠️ 经过详细代码审查,发现以下在文档中被遗漏的重大问题:**
|
||
|
||
#### 11.1 transfer.php中的mcrypt函数遗漏 ✅ 项目代码需修复
|
||
|
||
**文件位置**: `c:\webroot\game\api\source\apis\transfer.php`
|
||
|
||
**遗漏的函数**:
|
||
- `mcryptEncrypt()` 方法 (第328行)
|
||
- `mcryptDecrypt()` 方法 (第350行)
|
||
|
||
**修改前代码 - mcryptEncrypt方法**:
|
||
```php
|
||
public function mcryptEncrypt($input, $key)
|
||
{
|
||
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
|
||
$input = $this->pkcs_pad($input, $size);
|
||
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
|
||
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
|
||
mcrypt_generic_init($td, $key, $iv);
|
||
$data = mcrypt_generic($td, $input);
|
||
mcrypt_generic_deinit($td);
|
||
mcrypt_module_close($td);
|
||
$data = base64_encode($data);
|
||
return $data;
|
||
}
|
||
```
|
||
|
||
**修改后代码 - mcryptEncrypt方法**:
|
||
```php
|
||
public function mcryptEncrypt($input, $key)
|
||
{
|
||
// 使用OpenSSL AES-128-ECB替代mcrypt
|
||
$method = 'AES-128-ECB';
|
||
$input = $this->pkcs_pad($input, 16); // AES块大小为16字节
|
||
$data = openssl_encrypt($input, $method, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
$data = base64_encode($data);
|
||
return $data;
|
||
}
|
||
```
|
||
|
||
**修改前代码 - mcryptDecrypt方法**:
|
||
```php
|
||
public function mcryptDecrypt($sStr, $sKey)
|
||
{
|
||
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB), MCRYPT_RAND);
|
||
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $sKey, base64_decode($sStr), MCRYPT_MODE_ECB, $iv);
|
||
$dec_s = strlen($decrypted);
|
||
$padding = ord($decrypted[$dec_s-1]);
|
||
$decrypted = substr($decrypted, 0, -$padding);
|
||
return $decrypted;
|
||
}
|
||
```
|
||
|
||
**修改后代码 - mcryptDecrypt方法**:
|
||
```php
|
||
public function mcryptDecrypt($sStr, $sKey)
|
||
{
|
||
// 使用OpenSSL AES-128-ECB解密
|
||
$decrypted = openssl_decrypt(base64_decode($sStr), 'AES-128-ECB', $sKey, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
|
||
// ⚠️ 保持原有的自定义去填充逻辑
|
||
$dec_s = strlen($decrypted);
|
||
$padding = ord($decrypted[$dec_s-1]); // 直接取最后一个字节作为填充长度
|
||
$decrypted = substr($decrypted, 0, -$padding);
|
||
return $decrypted;
|
||
}
|
||
```
|
||
|
||
#### 11.2 Router.php中的each()函数使用 ✅ 项目代码需修复
|
||
|
||
**文件位置**: `c:\webroot\game\api\lib\phprs\Router.php:234`
|
||
|
||
**⚠️ 重要发现:文档之前建议的修改有功能差异!**
|
||
|
||
**修改前代码**:
|
||
```php
|
||
$geteach = function ()use(&$files){
|
||
$item = each($files);
|
||
if($item){
|
||
return $item[1]; // 返回each()结果的value部分
|
||
}else{
|
||
return false;
|
||
}
|
||
};
|
||
```
|
||
|
||
**修改后代码**:
|
||
```php
|
||
$geteach = function ()use(&$files){
|
||
// 保持与each()完全一致的行为
|
||
$key = key($files);
|
||
if($key === null) {
|
||
return false; // 数组指针已到末尾
|
||
}
|
||
|
||
$value = current($files);
|
||
next($files); // 移动指针到下一个元素
|
||
|
||
// 构造与each()相同的返回格式,但只需要$item[1]部分
|
||
return $value; // 直接返回值,因为原代码只使用$item[1]
|
||
};
|
||
```
|
||
|
||
**功能一致性验证**:
|
||
- **each()行为**:返回`array(0 => key, 1 => value, ...)`,原代码取`$item[1]`即value
|
||
- **新实现**:直接返回value,功能完全等价
|
||
- **指针移动**:使用`next()`确保指针正确移动
|
||
- **结束判断**:使用`key() === null`判断数组末尾
|
||
|
||
**测试验证代码**:
|
||
```php
|
||
// 验证原each()行为
|
||
$test_array = array('file1.php', 'file2.php', 'file3.php');
|
||
$original_results = array();
|
||
while(($item = each($test_array)) !== false) {
|
||
$original_results[] = $item[1];
|
||
}
|
||
|
||
// 测试新实现
|
||
$test_array = array('file1.php', 'file2.php', 'file3.php');
|
||
$new_results = array();
|
||
$geteach_new = function() use(&$test_array) {
|
||
$key = key($test_array);
|
||
if($key === null) return false;
|
||
$value = current($test_array);
|
||
next($test_array);
|
||
return $value;
|
||
};
|
||
while(($entry = $geteach_new()) !== false) {
|
||
$new_results[] = $entry;
|
||
}
|
||
|
||
// 验证结果必须完全一致
|
||
assert($original_results === $new_results, "Router.php each()替换功能验证失败");
|
||
```
|
||
|
||
**影响分析**:
|
||
- **功能影响**:路由文件遍历逻辑,影响API文件的加载
|
||
- **风险等级**:高 - 如果修改错误可能导致某些API文件无法加载
|
||
- **测试重点**:确保所有API文件都能正确加载
|
||
|
||
#### 11.3 common.inc.php的create_function替换过于简化
|
||
- **原有建议**:直接使用反射替换
|
||
- **问题**:忽略了复杂的动态参数处理逻辑
|
||
- **风险**:动态类实例创建可能失败
|
||
|
||
#### 11.4 填充算法一致性验证
|
||
|
||
**发现的潜在问题**: transfer.php中使用了自定义的`pkcs_pad()`方法,需要确保OpenSSL替换后填充行为完全一致。
|
||
|
||
**验证代码**:
|
||
```php
|
||
// 测试填充算法一致性
|
||
function testPaddingConsistency() {
|
||
$data = "test data";
|
||
$blockSize = 16;
|
||
|
||
// 原mcrypt行为(模拟)
|
||
$paddedData = pkcs_pad($data, $blockSize);
|
||
|
||
// OpenSSL + ZERO_PADDING + 自定义填充
|
||
$opensslData = openssl_encrypt($paddedData, 'AES-128-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
|
||
// 验证解密后是否一致
|
||
$decrypted = openssl_decrypt($opensslData, 'AES-128-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
$unpadded = pkcs_unpad($decrypted);
|
||
|
||
assert($data === $unpadded, "自定义双重填充算法测试失败");
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## <20> 重大发现:深度审查发现的关键功能一致性问题
|
||
|
||
**经过对项目代码的深入重新审查,发现了几个严重影响功能一致性的问题,必须在文档中明确并优先处理!**
|
||
|
||
### ⚠️ 问题1:transfer.php中严重的编码格式不匹配(原设计缺陷)
|
||
|
||
**🔍 重大发现**:`Crypt3Des`类的`encrypt()`和`decrypt()`方法**无法直接配对使用**!
|
||
|
||
**编码格式不匹配问题**:
|
||
- **encrypt()方法返回格式**:`$this->ToHex($data)` - 十六进制字符串
|
||
- **decrypt()方法输入期望**:`base64_decode($encrypted)` - Base64字符串
|
||
|
||
**验证代码**:
|
||
```php
|
||
$crypt = new Crypt3Des();
|
||
$crypt->key = "test_key";
|
||
$original = "test data";
|
||
|
||
// encrypt()返回十六进制
|
||
$encrypted_hex = $crypt->encrypt($original); // 例如:"A1B2C3D4..."
|
||
|
||
// decrypt()期望Base64输入,会解码失败!
|
||
$decrypted = $crypt->decrypt($encrypted_hex); // 返回null或错误数据!
|
||
|
||
// 正确的配对应该是:
|
||
$encrypted_b64 = base64_encode(hex2bin($encrypted_hex));
|
||
$decrypted = $crypt->decrypt($encrypted_b64); // 才能正确解密
|
||
```
|
||
|
||
**⚠️ 升级策略决定**:
|
||
- **按原则暂不修复**:这是原程序的设计缺陷,不是PHP8兼容性问题
|
||
- **保持原有"缺陷"**:mcrypt替换为OpenSSL后,必须保持相同的编码格式不匹配
|
||
- **升级后验证**:确保`encrypt()`仍返回Hex,`decrypt()`仍期望Base64,保持原有行为
|
||
|
||
### ⚠️ 问题2:transfer.php的自定义双重填充算法(必须严格保持)
|
||
|
||
**🔍 重要发现**:项目使用的`pkcs_pad()`不是标准PKCS填充!
|
||
|
||
**自定义双重填充实现分析**:
|
||
```php
|
||
protected function pkcs_pad($data, $blocksize)
|
||
{
|
||
// 第一步:标准PKCS填充
|
||
$pad = $blocksize - (strlen($data) % $blocksize);
|
||
$data .= str_repeat(chr($pad), $pad);
|
||
|
||
// 第二步:额外的零填充(非标准!)
|
||
$data_len = strlen($data);
|
||
if ($data_len % $blocksize)
|
||
$data = str_pad($data, $data_len + $blocksize - $data_len % $blocksize, "\0");
|
||
|
||
return $data;
|
||
}
|
||
```
|
||
|
||
**关键功能差异**:
|
||
- **mcryptEncrypt()/mcryptDecrypt()**:使用双重填充,但去填充只处理最后一个字节
|
||
- **encrypt()/decrypt()**:使用双重填充,但去填充使用pkcs5_unpad(实际调用pkcs_unpad)
|
||
|
||
**⚠️ 升级要求**:OpenSSL替换必须严格保持这种**非标准的双重填充算法**,不能使用OpenSSL标准PKCS填充。
|
||
|
||
### ⚠️ 问题3:Router.php的each()替换功能验证
|
||
|
||
**🔍 功能验证**:经过分析,Router.php中的each()用法相对简单,但必须确保指针移动行为完全一致。
|
||
|
||
**原始逻辑**:
|
||
```php
|
||
$geteach = function ()use(&$files){
|
||
$item = each($files);
|
||
if($item){
|
||
return $item[1]; // 只使用value部分
|
||
}else{
|
||
return false; // 数组结束
|
||
}
|
||
};
|
||
```
|
||
|
||
**替换后必须保证**:
|
||
- 数组指针移动行为完全一致
|
||
- 返回值(文件名)完全一致
|
||
- 结束条件判断完全一致
|
||
|
||
### ⚠️ 问题4:DatabaseHelper.php的PHP版本检查已经修正
|
||
|
||
**🔍 重要发现**:文档中声明的第8744行`call_user_method`检查问题**在当前代码中已不存在**!
|
||
|
||
**当前实际代码**(第8744行):
|
||
```php
|
||
if (!(function_exists('call_user_func') && function_exists('call_user_func_array')))
|
||
throw new Exception('Don\'t support the current version. The code requires PHP 5.0 or later.');
|
||
```
|
||
|
||
**文档错误**:文档中提到的`call_user_method`和`call_user_method_array`检查**已经被修正**,当前代码只检查PHP8仍支持的函数。
|
||
|
||
**结论**:此文件**无需修改**,版本检查已经是PHP8兼容的。
|
||
|
||
### ⚠️ 问题5:ntunnel_mysql.php的magic quotes处理
|
||
|
||
**🔍 现状确认**:
|
||
```php
|
||
// 第20行:
|
||
if (phpversion_int() < 50300) {
|
||
set_magic_quotes_runtime(0); // 仅在PHP5.3以下调用
|
||
}
|
||
|
||
// 第272行和331行:
|
||
if (phpversion_int() < 50400) {
|
||
if(get_magic_quotes_gpc())
|
||
$query = stripslashes($query);
|
||
}
|
||
```
|
||
|
||
**评估结果**:
|
||
- 代码已有PHP版本检查保护
|
||
- 在PHP8环境下,`phpversion_int() < 50400`为false,不会调用magic quotes函数
|
||
- **无需修改**:现有版本检查已提供充分保护
|
||
|
||
## <20>🚀 升级实施建议
|
||
|
||
### 实施阶段规划
|
||
|
||
#### 阶段一:环境准备(预计1-2天)
|
||
1. **备份当前环境**
|
||
- 完整代码备份
|
||
- 数据库备份
|
||
- 配置文件备份
|
||
- 建立回滚方案
|
||
|
||
2. **搭建测试环境**
|
||
- 安装PHP8环境
|
||
- 配置相同的Web服务器设置
|
||
- 导入测试数据
|
||
|
||
#### 阶段二:核心功能修复(预计3-5天)
|
||
|
||
**🚫 重要提醒:严禁修改以下目录下的任何文件!**
|
||
- `game\dlweb\api\third\` - 第三方库目录
|
||
- `game\api\payment\` - 支付模块目录
|
||
|
||
**按优先级排序的修复计划**:
|
||
|
||
1. **第一优先级(P1)**:核心加密相关修复(仅项目代码)
|
||
- global.func.php中的微信AES-128-CBC
|
||
- transfer.php中的3DES和AES加密(新增的mcrypt方法)
|
||
|
||
2. **第二优先级(P2)**:遍历和路由逻辑(项目代码)
|
||
- Router.php中的each()使用
|
||
- 其他支付模块的each()使用(兼容层处理)
|
||
|
||
3. **第三优先级(P3)**:动态函数创建(项目代码)
|
||
- common.inc.php的create_function()替换
|
||
- DatabaseHelper.php相关处理
|
||
|
||
4. **第四优先级(P4)**:数据库兼容性(项目代码)
|
||
- MySQL扩展替换(ntunnel_mysql.php等)
|
||
- DatabaseHelper.php的版本检查
|
||
|
||
5. **第五优先级(P5)**:配置和环境调整
|
||
- PHP版本检查修复(已完成)
|
||
- 全局变量处理
|
||
|
||
6. **最低优先级(P6)**:安全性问题(暂缓处理)
|
||
- **preg_replace /e修饰符问题**:暂不修改
|
||
- 如影响升级,考虑兼容层或环境配置
|
||
|
||
**特殊处理**:不修改目录的兼容性处理
|
||
- 🚫 **不修改** 第三方库(third目录)
|
||
- 🚫 **不修改** 支付模块(payment目录)
|
||
- ✅ 安装mcrypt兼容扩展
|
||
- ✅ 部署each()等兼容函数
|
||
- ✅ 配置环境兼容性设置
|
||
|
||
#### 阶段三:专项测试(预计2-3天)
|
||
1. **加密功能测试**
|
||
- 与微信支付的加密通信测试
|
||
- 支付宝支付流程完整测试
|
||
- Cookie加密解密对比测试
|
||
|
||
2. **API接口测试**
|
||
- 淘宝/阿里云API调用测试
|
||
- 游戏API各端点功能测试
|
||
- 第三方登录接口测试
|
||
|
||
3. **模板渲染测试**
|
||
- 所有页面模板渲染对比
|
||
- 动态生成PHP代码验证
|
||
- 特殊字符处理测试
|
||
|
||
#### 阶段四:性能和稳定性验证(预计1-2天)
|
||
1. **性能基准测试**
|
||
- 关键API响应时间对比
|
||
- 数据库查询性能测试
|
||
- 内存使用情况监控
|
||
|
||
2. **压力测试**
|
||
- 并发用户访问测试
|
||
- 长时间运行稳定性
|
||
- 异常情况恢复能力
|
||
|
||
### 升级后监控重点
|
||
|
||
#### 关键指标监控
|
||
- **支付成功率**:必须保持在99%以上
|
||
- **API响应时间**:不超过原系统1.2倍
|
||
- **加密操作成功率**:100%成功
|
||
- **模板渲染错误率**:接近0%
|
||
|
||
#### 日志监控
|
||
```php
|
||
// 添加升级后监控日志
|
||
error_log("[PHP8_UPGRADE_MONITOR] 加密操作: " . ($success ? "成功" : "失败"));
|
||
error_log("[PHP8_UPGRADE_MONITOR] API调用: {$api_name} - " . ($result ? "成功" : "失败"));
|
||
error_log("[PHP8_UPGRADE_MONITOR] 模板渲染: {$template} - 耗时: {$time}ms");
|
||
```
|
||
|
||
#### 紧急回滚触发条件
|
||
- 支付成功率低于95%
|
||
- 严重错误数量超过每小时10次
|
||
- 关键API响应时间超过原系统2倍
|
||
- 数据不一致问题出现
|
||
|
||
### 长期维护建议
|
||
|
||
1. **代码质量提升**
|
||
- 逐步引入类型声明
|
||
- 使用现代PHP特性重构老代码
|
||
- 建立单元测试覆盖
|
||
|
||
2. **安全性增强**
|
||
- 利用PHP8的安全性改进
|
||
- 更新加密算法到现代标准
|
||
- 加强输入验证和过滤
|
||
|
||
3. **性能优化**
|
||
- 利用PHP8的JIT编译器
|
||
- 优化数据库查询
|
||
- 缓存策略改进
|
||
|
||
---
|
||
|
||
## 📋 按PHP8兼容性原则的最终升级清单(重新审查后v4.8)
|
||
|
||
### ✅ 最终确认需要修改的文件
|
||
|
||
#### P1:PHP8移除的函数/扩展(6个文件,已移除不需要的项目)
|
||
1. **transfer.php** - mcrypt扩展替换为OpenSSL(4个方法:encrypt, decrypt, mcryptEncrypt, mcryptDecrypt)
|
||
2. **Router.php** - each()函数替换(1处)
|
||
3. **common.inc.php** - create_function()替换(2处)
|
||
4. **global.func.php** - 微信AES加密mcrypt替换(aes_encode, aes_decode函数)
|
||
|
||
#### P2:PHP8废弃的语法(2个文件,已确认)
|
||
1. **transfer.php** - 字符串大括号访问语法:`$data{strlen($data)-1}` 改为 `$data[strlen($data)-1]`
|
||
|
||
#### 第三方库兼容(兼容层处理)
|
||
1. **全局兼容函数** - split()、each()等
|
||
2. **不修改源码** - 所有third和payment目录文件
|
||
|
||
### ❌ 明确不需要修改的项目(重新审查后确认)
|
||
|
||
#### 已确认无需修改的文件:
|
||
1. **DatabaseHelper.php** - ✅ **版本检查已修正**,当前代码只检查PHP8支持的函数
|
||
2. **ntunnel_mysql.php** - ✅ **已有版本保护**,PHP8环境下不会调用magic quotes函数
|
||
|
||
#### 确认暂不修改的问题:
|
||
1. **编码格式不匹配**:Crypt3Des类的encrypt()返回Hex但decrypt()期望Base64
|
||
2. **自定义双重填充**:非标准的pkcs_pad实现必须保持
|
||
3. **去填充逻辑差异**:不同方法的去填充处理必须保持原有差异
|
||
4. **安全性问题**:preg_replace /e修饰符等
|
||
5. **第三方库问题**:third和payment目录下的所有兼容性问题
|
||
|
||
### 🎯 重新审查后的修改范围大幅缩小
|
||
|
||
**原文档声明需修改:13个文件**
|
||
**重新审查后实际需修改:4个文件**
|
||
|
||
| 文件 | 原声明 | 审查结果 | 修改原因 |
|
||
|------|--------|----------|----------|
|
||
| transfer.php | ✅ 需修改 | ✅ **确需修改** | mcrypt + 字符串语法 |
|
||
| Router.php | ✅ 需修改 | ✅ **确需修改** | each()函数 |
|
||
| common.inc.php | ✅ 需修改 | ✅ **确需修改** | create_function() |
|
||
| global.func.php | ✅ 需修改 | ✅ **确需修改** | 微信AES mcrypt |
|
||
| DatabaseHelper.php | 🚫 文档错误 | ❌ **无需修改** | 版本检查已修正 |
|
||
| ntunnel_mysql.php | 🚫 文档错误 | ❌ **无需修改** | 已有版本保护 |
|
||
|
||
**🎯 结论**:
|
||
- **修改文件数从6个减少到4个**
|
||
- **所有修改都是真正的PHP8不兼容问题**
|
||
- **功能一致性风险大幅降低**
|
||
- **升级范围更加精确和安全**
|
||
|
||
**🎯 核心原则重申**:
|
||
- 只修改PHP8不支持的API和语法
|
||
- 保持所有原有功能特征,包括"缺陷"
|
||
- 改动后功能必须与原系统100%一致
|
||
|
||
## 📋 文档版本信息
|
||
|
||
**文档版本**:v4.8 - 深度重新审查代码,纠正文档错误,精确定位真正需修改的问题
|
||
**最后更新**:移除不必要的修改项目,将修改文件数从6个减少到4个,大幅降低升级风险
|
||
**状态**:✅ 重新审查完成 - 确认仅4个文件需修改PHP8兼容性问题
|
||
|
||
## ✅ 重新审查后的最终确认声明
|
||
|
||
**经过对项目代码的深入重新审查,发现文档存在多处错误,现已全面纠正**:
|
||
|
||
### 🔧 修正的重大错误:
|
||
|
||
1. **DatabaseHelper.php误报**:
|
||
- ❌ **文档错误**:声称第8744行有`call_user_method`检查需修复
|
||
- ✅ **实际情况**:代码已修正,只检查PHP8支持的`call_user_func`等函数
|
||
- 📝 **结论**:无需修改
|
||
|
||
2. **ntunnel_mysql.php误报**:
|
||
- ❌ **文档错误**:声称magic quotes函数需要兼容处理
|
||
- ✅ **实际情况**:代码已有`phpversion_int() < 50400`版本保护
|
||
- 📝 **结论**:PHP8环境下不会调用magic quotes函数,无需修改
|
||
|
||
3. **修改范围大幅精简**:
|
||
- **原声明**:6-8个文件需修改
|
||
- **实际需求**:仅4个文件需修改PHP8兼容性问题
|
||
|
||
### 🎯 真正需要修改的PHP8兼容性问题:
|
||
|
||
#### 必须修改的4个文件:
|
||
1. **transfer.php**:
|
||
- mcrypt扩展替换(4个方法)
|
||
- 字符串大括号访问语法(1处)
|
||
|
||
2. **Router.php**:
|
||
- each()函数替换(1处)
|
||
|
||
3. **common.inc.php**:
|
||
- create_function()替换(2处)
|
||
|
||
4. **global.func.php**:
|
||
- 微信AES加密mcrypt替换(2个函数)
|
||
|
||
#### 必须保持的原有"特征":
|
||
1. **编码格式不匹配**:encrypt()返回Hex,decrypt()期望Base64
|
||
2. **自定义双重填充**:非标准的pkcs_pad实现必须保持
|
||
3. **去填充逻辑差异**:不同方法的去填充处理必须保持原有差异
|
||
|
||
### 🚀 升级风险评估:
|
||
|
||
- **风险等级**:从中高风险降低为**中低风险**
|
||
- **修改范围**:大幅缩小,仅涉及真正的PHP8不兼容问题
|
||
- **功能一致性**:所有原有特征(包括设计缺陷)都将严格保持
|
||
- **测试重点**:4个文件的加密、遍历、动态函数功能验证
|
||
|
||
**✅ 最终结论:经过深度重新审查,PHP8升级的实际修改范围比文档原声明的小得多。仅需修改4个文件中的真正PHP8兼容性问题,所有原有功能特征(包括编码格式不匹配等设计问题)都将严格保持,确保升级后功能与原系统100%一致。这种精确的修改范围大幅降低了升级风险,提高了成功率。**
|
||
|
||
---
|
||
|
||
## 🚨 深度审查发现的关键功能一致性问题(v4.7补充)
|
||
|
||
### ⚠️ 重大发现:去填充逻辑的差异化实现
|
||
|
||
通过重新审查实际代码,发现transfer.php中存在**两种不同的去填充逻辑**,这是原设计的功能差异,必须严格保持:
|
||
|
||
#### 1. mcryptDecrypt的简单去填充(AES方法)
|
||
```php
|
||
// mcryptDecrypt中的实现(AES-128-ECB)
|
||
$dec_s = strlen($decrypted);
|
||
$padding = ord($decrypted[$dec_s-1]); // 直接取最后一个字节
|
||
$decrypted = substr($decrypted, 0, -$padding);
|
||
```
|
||
|
||
#### 2. pkcs_unpad的完整PKCS验证(3DES方法)
|
||
```php
|
||
// pkcs_unpad中的实现(通过pkcs5_unpad调用)
|
||
$pad = ord($data{strlen($data)-1}); // ⚠️ 需要改为方括号
|
||
if ($pad > strlen($data)) return null; // 长度验证
|
||
if (strspn($data, chr($pad), strlen($data) - $pad) != $pad) return null; // PKCS完整性验证
|
||
return substr($data, 0, -1 * $pad);
|
||
```
|
||
|
||
### ✅ 正确的修改方案(保持功能差异)
|
||
|
||
#### 修改1:pkcs_unpad字符串大括号语法(必须修改)
|
||
```php
|
||
protected function pkcs_unpad($data)
|
||
{
|
||
$pad = ord($data[strlen($data)-1]); // ✅ 仅修改大括号为方括号
|
||
if ($pad > strlen($data))
|
||
return null;
|
||
if (strspn($data, chr($pad), strlen($data) - $pad) != $pad)
|
||
return null;
|
||
return substr($data, 0, -1 * $pad);
|
||
}
|
||
```
|
||
|
||
#### 修改2:Crypt3Des.encrypt()保持pkcs_pad+pkcs5_unpad配对
|
||
```php
|
||
public function encrypt($input)
|
||
{
|
||
if (empty($input)) return null;
|
||
|
||
$size = 8; // 3DES块大小
|
||
$input = $this->pkcs_pad($input, $size); // 自定义双重填充
|
||
$key = str_pad($this->key, 24, '0');
|
||
|
||
// 使用OpenSSL,保持与mcrypt相同的填充处理
|
||
$data = openssl_encrypt($input, 'des-ede3', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
return $this->ToHex($data); // 保持十六进制输出
|
||
}
|
||
```
|
||
|
||
#### 修改3:Crypt3Des.decrypt()保持base64输入+pkcs5_unpad
|
||
```php
|
||
public function decrypt($encrypted)
|
||
{
|
||
if (!$encrypted || empty($encrypted)) return null;
|
||
|
||
$encrypted = base64_decode($encrypted); // 保持Base64输入期望
|
||
if (!$encrypted || empty($encrypted)) return null;
|
||
|
||
$key = str_pad($this->key, 24, '0');
|
||
$decrypted = openssl_decrypt($encrypted, 'des-ede3', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
|
||
$y = $this->pkcs5_unpad($decrypted); // 保持完整PKCS验证
|
||
return $y;
|
||
}
|
||
```
|
||
|
||
#### 修改4:AesCrypt.mcryptDecrypt()保持简单去填充
|
||
```php
|
||
public function mcryptDecrypt($sStr, $sKey)
|
||
{
|
||
$decrypted = openssl_decrypt(base64_decode($sStr), 'AES-128-ECB', $sKey, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
|
||
|
||
// ⚠️ 保持原有的简单去填充逻辑(不同于pkcs_unpad)
|
||
$dec_s = strlen($decrypted);
|
||
$padding = ord($decrypted[$dec_s-1]); // 直接取最后字节,无验证
|
||
$decrypted = substr($decrypted, 0, -$padding);
|
||
return $decrypted;
|
||
}
|
||
```
|
||
|
||
### 🎯 关键原则重申
|
||
|
||
1. **保持填充算法差异**:不同方法使用不同的去填充逻辑是原设计特征
|
||
2. **严格配对关系**:
|
||
- Crypt3Des方法 ↔ pkcs5_unpad(完整验证)
|
||
- AesCrypt方法 ↔ 简单去填充(无验证)
|
||
3. **只修改语法问题**:仅将`$data{}`改为`$data[]`,不改变任何逻辑
|
||
|
||
### ⚠️ 功能验证要求
|
||
|
||
```php
|
||
// 测试3DES加密往返(保持编码不匹配特征)
|
||
$crypt3des = new Crypt3Des();
|
||
$crypt3des->key = "test_key_123";
|
||
$original = "test message for verification";
|
||
|
||
// 测试encrypt()返回格式(必须是Hex)
|
||
$encrypted_old = $crypt3des->encrypt($original); // mcrypt版本
|
||
$encrypted_new = $crypt3des->encrypt($original); // OpenSSL版本
|
||
assert(ctype_xdigit($encrypted_old), "encrypt()必须返回十六进制");
|
||
assert(ctype_xdigit($encrypted_new), "升级后encrypt()必须仍返回十六进制");
|
||
|
||
// 验证decrypt()期望Base64输入的行为
|
||
$base64_input = base64_encode(hex2bin($encrypted_old));
|
||
$decrypted_old = $crypt3des->decrypt($base64_input); // mcrypt版本
|
||
$decrypted_new = $crypt3des->decrypt($base64_input); // OpenSSL版本
|
||
assert($decrypted_old === $decrypted_new, "解密结果必须完全一致");
|
||
|
||
// 验证编码格式不匹配仍然存在(原有"缺陷"必须保持)
|
||
$wrong_decrypt = $crypt3des->decrypt($encrypted_old); // 直接传Hex给decrypt
|
||
assert($wrong_decrypt === null || $wrong_decrypt === false, "编码不匹配问题必须保持");
|
||
```
|
||
|
||
**mcryptEncrypt/mcryptDecrypt方法验证**:
|
||
```php
|
||
// 验证AES加密解密功能一致性
|
||
$key = "1234567890123456"; // 16字节AES密钥
|
||
$message = "AES test message";
|
||
|
||
$encrypted_old = $transfer->mcryptEncrypt($message, $key); // mcrypt版本
|
||
$encrypted_new = $transfer->mcryptEncrypt($message, $key); // OpenSSL版本
|
||
|
||
$decrypted_old = $transfer->mcryptDecrypt($encrypted_old, $key);
|
||
$decrypted_new = $transfer->mcryptDecrypt($encrypted_new, $key);
|
||
|
||
assert($decrypted_old === $message, "原mcrypt解密必须成功");
|
||
assert($decrypted_new === $message, "升级后解密必须成功");
|
||
assert($decrypted_old === $decrypted_new, "解密结果必须完全一致");
|
||
```
|
||
|
||
#### Router.php功能验证方案:
|
||
|
||
**each()替换验证**:
|
||
```php
|
||
// 验证文件遍历行为完全一致
|
||
$test_files = ['api1.php', 'api2.php', 'api3.php', 'subdir/api4.php'];
|
||
|
||
// 模拟原each()行为
|
||
$files_old = $test_files;
|
||
$results_old = [];
|
||
$geteach_old = function() use(&$files_old) {
|
||
$item = each($files_old);
|
||
return $item ? $item[1] : false;
|
||
};
|
||
while(($entry = $geteach_old()) !== false) {
|
||
$results_old[] = $entry;
|
||
}
|
||
|
||
// 测试新实现
|
||
$files_new = $test_files;
|
||
$results_new = [];
|
||
$geteach_new = function() use(&$files_new) {
|
||
$key = key($files_new);
|
||
if($key === null) return false;
|
||
$value = current($files_new);
|
||
next($files_new);
|
||
return $value;
|
||
};
|
||
while(($entry = $geteach_new()) !== false) {
|
||
$results_new[] = $entry;
|
||
}
|
||
|
||
assert($results_old === $results_new, "文件遍历结果必须完全一致");
|
||
```
|
||
|
||
#### common.inc.php功能验证方案:
|
||
|
||
**create_function()替换验证**:
|
||
```php
|
||
// 验证动态类实例创建功能一致性
|
||
class TestClass {
|
||
public $params;
|
||
public function __construct() {
|
||
$this->params = func_get_args();
|
||
}
|
||
}
|
||
|
||
// 测试不同参数数量
|
||
$test_cases = [
|
||
['TestClass'],
|
||
['TestClass', 'param1'],
|
||
['TestClass', 'param1', 'param2'],
|
||
['TestClass', 'param1', 'param2', 'param3']
|
||
];
|
||
|
||
foreach($test_cases as $case) {
|
||
$obj_old = call_user_func_array([$helper, 'NewInstance'], $case); // 原版本
|
||
$obj_new = call_user_func_array([$helper, 'NewInstance'], $case); // 升级版本
|
||
assert($obj_old->params === $obj_new->params, "动态实例创建参数必须一致");
|
||
}
|
||
```
|
||
|
||
#### global.func.php功能验证方案:
|
||
|
||
**微信AES加密验证**:
|
||
```php
|
||
// 验证微信协议兼容性
|
||
$message = "微信消息测试内容";
|
||
$encodingaeskey = "test_encoding_aes_key_for_wechat_protocol";
|
||
$appid = "test_app_id";
|
||
|
||
$encrypted_old = aes_encode($message, $encodingaeskey, $appid); // mcrypt版本
|
||
$encrypted_new = aes_encode($message, $encodingaeskey, $appid); // OpenSSL版本
|
||
|
||
$decrypted_old = aes_decode($encrypted_old, $encodingaeskey, $appid);
|
||
$decrypted_new = aes_decode($encrypted_new, $encodingaeskey, $appid);
|
||
|
||
assert($decrypted_old === $message, "原微信解密必须成功");
|
||
assert($decrypted_new === $message, "升级后微信解密必须成功");
|
||
assert($decrypted_old === $decrypted_new, "微信协议处理必须完全一致");
|
||
```
|
||
|
||
**⚠️ 验证失败处理**:
|
||
- 任何功能验证失败都必须停止升级
|
||
- 必须分析差异原因并调整OpenSSL实现
|
||
- 确保所有原有行为(包括"缺陷")100%保持
|
||
|
||
---
|