54 KiB
PHP8 升级兼容性文档
项目概述
本文档详细记录了将游戏API项目从旧版PHP升级到PHP8所需的所有修改。项目主要包含游戏API接口、支付接口、第三方服务集成等功能。
📋 修改原则与标准
🎯 核心原则(重新明确v4.7)
- 仅修改PHP8不兼容问题:只修改PHP8中已移除的API、函数、语法等导致程序无法运行的问题
- 功能完全一致性:改动后功能必须与原系统100%一致,不能有任何功能差异
- 不修改安全性问题:纯安全性问题但不影响程序正确执行的暂不修改
- 不修改程序逻辑问题:原程序的代码逻辑错误、设计缺陷等暂不修复
- 第三方库隔离:third和payment目录下的文件不做任何修改
🔍 问题分类标准(按优先级排序)
🔍 问题分类标准(按PHP8兼容性要求重新分类)
问题类型定义
-
P1:PHP8移除的函数/扩展(必须修改)
- mcrypt扩展:PHP7.2移除,PHP8完全不可用
- each()函数:PHP8移除
- create_function()函数:PHP8移除
- split()函数:PHP7.0移除
- magic quotes函数:PHP7.4移除
-
P2:PHP8废弃的语法(必须修改)
- 字符串大括号访问:
$str{0}必须改为$str[0] - 按引用传递对象语法变化:
array(&$obj)不推荐 - 函数参数顺序问题:
implode()参数顺序
- 字符串大括号访问:
-
P3:第三方库兼容性(兼容层处理)
- 不修改源码:使用兼容函数或升级版本
-
P4:安全性问题(暂不修改)
- preg_replace /e修饰符:安全风险但程序仍能执行
- 模板引擎安全问题:输入源可控,暂不修改
-
P5:程序逻辑问题(暂不修改)
- 编码格式不匹配:如encrypt()返回Hex但decrypt()期望Base64
- 类方法不匹配:如Crypt3Des缺少Encrypt()方法
- 其他设计缺陷:原程序的逻辑问题暂不修复
✅ 修改目标
- 加密/解密功能与原系统完全一致
- 数据处理和遍历逻辑与原系统完全一致
- 正则表达式处理结果与原系统完全一致
- 类型转换和验证与原系统完全一致
- 所有业务流程与原系统完全一致
⚠️ 重要声明:不修改目录原则
🚫 严禁修改以下目录下的任何文件!
用户明确要求:
game\dlweb\api\third\下的三方库不需要修改和升级game\api\payment\下的支付相关PHP文件也不修改影响文件包括但不限于:
third/taobao/aliyun/AliyunClient.phpthird/taobao/top/SpiUtils.phpthird/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/) - 其他支付接口
处理策略:
- 寻找官方PHP8兼容版本
- 使用兼容层函数
- 联系厂商获取升级支持
- 绝不直接修改第三方库或支付模块源码
发现的兼容性问题汇总
问题分类概览(按优先级排序)
问题分类概览(按PHP8兼容性重新分类)
| 优先级 | 类别 | 问题类型 | 影响文件数 | 处理策略 | 修改原因 |
|---|---|---|---|---|---|
| P1 | 🔧 PHP8移除的函数 | mcrypt/each/create_function等 | 8个文件 | 必须修改 | PHP8不支持,程序无法运行 |
| P2 | <EFBFBD> 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中被移除。
修改前代码:
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.');
修改后代码:
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 处理策略:
// 这些是第三方库文件,不应该直接修改
// 建议的处理方案:
// 方案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:1312c:\webroot\game\dlweb\api\common\common.inc.php:1315c:\webroot\game\api\payment\ipay\base.php:102(🚫 支付模块,暂不修改)
问题描述:
create_function() 在PHP 7.2中被弃用,在PHP 8.0中被移除。需要替换为匿名函数(闭包)。
⚠️ 重要说明: payment目录下的文件按用户要求暂不修改,只处理项目核心代码。
3.1 common.inc.php 第一处修改
修改前代码:
array_walk($keys, create_function('&$value, $key, $prefix', '$value = $prefix . $value;'), '$arg_');
修改后代码:
array_walk($keys, function(&$value, $key, $prefix) { $value = $prefix . $value; }, '$arg_');
功能说明:
- 用于给数组的每个键名添加前缀 '$arg_'
- 为动态创建类实例时的参数处理做准备
3.2 common.inc.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);
}
原始逻辑详细分析:
- 假设调用:
NewInstance('MyClass', 'param1', 'param2', 'param3') $arguments = ['param1', 'param2', 'param3'](classname被shift出去)$keys = [0, 1, 2]- array_walk后:
$keys = ['$arg_0', '$arg_1', '$arg_2'] $paramstr = '$arg_0, $arg_1, $arg_2'- create_function创建:
function($arg_0, $arg_1, $arg_2) { return new MyClass($arg_0, $arg_1, $arg_2); } - call_user_func_array调用该函数,传入实际参数值
⚠️ 之前错误的修改建议:
// 这个修改丢失了动态参数处理的核心功能!
private function NewInstance()
{
$arguments = func_get_args();
$classname = array_shift($arguments);
$reflection = new ReflectionClass($classname);
return $reflection->newInstanceArgs($arguments);
}
正确的修改建议:
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();
}
更简洁的正确修改:
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);
}
功能一致性验证:
// 测试不同参数数量的类实例创建
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
修改前代码:
$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()兼容函数:
// 兼容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:
// ntunnel_mysql.php中
set_magic_quotes_runtime(0);
修改后代码1:
// 在PHP 7.4+中不再需要,可以删除或添加版本检查
if (function_exists('set_magic_quotes_runtime')) {
set_magic_quotes_runtime(0);
}
修改前代码2 - 检查magic quotes状态:
// ntunnel_mysql.php中(需要修复)
if(get_magic_quotes_gpc())
{
$arg = stripslashes($arg);
}
// alipay_core.function.php中(暂不修改,使用兼容函数)
if(get_magic_quotes_gpc())
{
$arg = stripslashes($arg);
}
修改后代码2:
// 项目代码修复(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方法清单:
encrypt()- 3DES-ECB加密 (文档遗漏!)decrypt()- 3DES-ECB解密 (文档遗漏!)mcryptEncrypt()- AES-128-ECB加密 (文档已提及)mcryptDecrypt()- AES-128-ECB解密 (文档已提及)
⚠️ 重大问题2:ECB模式IV处理的功能差异
原始代码特点:
// 所有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函数实现:
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;
}
问题分析:
- 双重填充问题:先做PKCS填充,再做零填充
- 与标准PKCS不符:标准PKCS填充不应该有额外的零填充
- OpenSSL兼容性风险:OpenSSL的PKCS填充是标准实现,与此不同
填充方法对应关系:
encrypt()方法:使用pkcs_pad($input, $size)(含双重填充)decrypt()方法:使用pkcs5_unpad($decrypted)(标准PKCS去填充)mcryptEncrypt():使用pkcs_pad($input, $size)(含双重填充)mcryptDecrypt():使用自定义去填充逻辑 (直接截取padding长度)
🚨 功能一致性风险:
- 填充算法不标准:项目自定义的填充算法与OpenSSL标准不兼容
- mcrypt特殊行为:mcrypt可能可以处理这种双重填充,但OpenSSL不行
- 解密失败风险:直接替换为OpenSSL标准PKCS填充会导致解密失败
正确的修改方案(重新设计 - 必须保持双重填充逻辑)
⚠️ 关键发现:项目使用的不是标准PKCS填充,而是自定义的双重填充算法!
🚨 新发现的严重问题:encrypt()和decrypt()方法的编码格式不匹配!
- encrypt()返回:
ToHex()格式(十六进制字符串) - decrypt()期望:
base64_decode()格式(Base64字符串) - 严重后果:这两个方法无法直接配对使用!
修改前代码1 - encrypt方法(3DES-ECB):
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 - 必须保持十六进制输出格式:
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):
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输入格式:
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):
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加密:
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):
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 - 保持自定义去填充逻辑:
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函数:
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);
// ...后续处理逻辑
}
修改后代码 - 完整的微信解密:
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函数:
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;
}
修改后代码 - 保持微信加密协议:
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方法:
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方法:
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方法:
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方法:
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
⚠️ 重要发现:文档之前建议的修改有功能差异!
修改前代码:
$geteach = function ()use(&$files){
$item = each($files);
if($item){
return $item[1]; // 返回each()结果的value部分
}else{
return false;
}
};
修改后代码:
$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判断数组末尾
测试验证代码:
// 验证原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替换后填充行为完全一致。
验证代码:
// 测试填充算法一致性
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, "自定义双重填充算法测试失败");
}
<EFBFBD> 重大发现:深度审查发现的关键功能一致性问题
经过对项目代码的深入重新审查,发现了几个严重影响功能一致性的问题,必须在文档中明确并优先处理!
⚠️ 问题1:transfer.php中严重的编码格式不匹配(原设计缺陷)
🔍 重大发现:Crypt3Des类的encrypt()和decrypt()方法无法直接配对使用!
编码格式不匹配问题:
- encrypt()方法返回格式:
$this->ToHex($data)- 十六进制字符串 - decrypt()方法输入期望:
base64_decode($encrypted)- Base64字符串
验证代码:
$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填充!
自定义双重填充实现分析:
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()用法相对简单,但必须确保指针移动行为完全一致。
原始逻辑:
$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行):
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处理
🔍 现状确认:
// 第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函数 - 无需修改:现有版本检查已提供充分保护
<EFBFBD>🚀 升级实施建议
实施阶段规划
阶段一:环境准备(预计1-2天)
-
备份当前环境
- 完整代码备份
- 数据库备份
- 配置文件备份
- 建立回滚方案
-
搭建测试环境
- 安装PHP8环境
- 配置相同的Web服务器设置
- 导入测试数据
阶段二:核心功能修复(预计3-5天)
🚫 重要提醒:严禁修改以下目录下的任何文件!
game\dlweb\api\third\- 第三方库目录game\api\payment\- 支付模块目录
按优先级排序的修复计划:
-
第一优先级(P1):核心加密相关修复(仅项目代码)
- global.func.php中的微信AES-128-CBC
- transfer.php中的3DES和AES加密(新增的mcrypt方法)
-
第二优先级(P2):遍历和路由逻辑(项目代码)
- Router.php中的each()使用
- 其他支付模块的each()使用(兼容层处理)
-
第三优先级(P3):动态函数创建(项目代码)
- common.inc.php的create_function()替换
- DatabaseHelper.php相关处理
-
第四优先级(P4):数据库兼容性(项目代码)
- MySQL扩展替换(ntunnel_mysql.php等)
- DatabaseHelper.php的版本检查
-
第五优先级(P5):配置和环境调整
- PHP版本检查修复(已完成)
- 全局变量处理
-
最低优先级(P6):安全性问题(暂缓处理)
- preg_replace /e修饰符问题:暂不修改
- 如影响升级,考虑兼容层或环境配置
特殊处理:不修改目录的兼容性处理
- 🚫 不修改 第三方库(third目录)
- 🚫 不修改 支付模块(payment目录)
- ✅ 安装mcrypt兼容扩展
- ✅ 部署each()等兼容函数
- ✅ 配置环境兼容性设置
阶段三:专项测试(预计2-3天)
-
加密功能测试
- 与微信支付的加密通信测试
- 支付宝支付流程完整测试
- Cookie加密解密对比测试
-
API接口测试
- 淘宝/阿里云API调用测试
- 游戏API各端点功能测试
- 第三方登录接口测试
-
模板渲染测试
- 所有页面模板渲染对比
- 动态生成PHP代码验证
- 特殊字符处理测试
阶段四:性能和稳定性验证(预计1-2天)
-
性能基准测试
- 关键API响应时间对比
- 数据库查询性能测试
- 内存使用情况监控
-
压力测试
- 并发用户访问测试
- 长时间运行稳定性
- 异常情况恢复能力
升级后监控重点
关键指标监控
- 支付成功率:必须保持在99%以上
- API响应时间:不超过原系统1.2倍
- 加密操作成功率:100%成功
- 模板渲染错误率:接近0%
日志监控
// 添加升级后监控日志
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倍
- 数据不一致问题出现
长期维护建议
-
代码质量提升
- 逐步引入类型声明
- 使用现代PHP特性重构老代码
- 建立单元测试覆盖
-
安全性增强
- 利用PHP8的安全性改进
- 更新加密算法到现代标准
- 加强输入验证和过滤
-
性能优化
- 利用PHP8的JIT编译器
- 优化数据库查询
- 缓存策略改进
📋 按PHP8兼容性原则的最终升级清单(重新审查后v4.8)
✅ 最终确认需要修改的文件
P1:PHP8移除的函数/扩展(6个文件,已移除不需要的项目)
- transfer.php - mcrypt扩展替换为OpenSSL(4个方法:encrypt, decrypt, mcryptEncrypt, mcryptDecrypt)
- Router.php - each()函数替换(1处)
- common.inc.php - create_function()替换(2处)
- global.func.php - 微信AES加密mcrypt替换(aes_encode, aes_decode函数)
P2:PHP8废弃的语法(2个文件,已确认)
- transfer.php - 字符串大括号访问语法:
$data{strlen($data)-1}改为$data[strlen($data)-1]
第三方库兼容(兼容层处理)
- 全局兼容函数 - split()、each()等
- 不修改源码 - 所有third和payment目录文件
❌ 明确不需要修改的项目(重新审查后确认)
已确认无需修改的文件:
- DatabaseHelper.php - ✅ 版本检查已修正,当前代码只检查PHP8支持的函数
- ntunnel_mysql.php - ✅ 已有版本保护,PHP8环境下不会调用magic quotes函数
确认暂不修改的问题:
- 编码格式不匹配:Crypt3Des类的encrypt()返回Hex但decrypt()期望Base64
- 自定义双重填充:非标准的pkcs_pad实现必须保持
- 去填充逻辑差异:不同方法的去填充处理必须保持原有差异
- 安全性问题:preg_replace /e修饰符等
- 第三方库问题: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兼容性问题
✅ 重新审查后的最终确认声明
经过对项目代码的深入重新审查,发现文档存在多处错误,现已全面纠正:
🔧 修正的重大错误:
-
DatabaseHelper.php误报:
- ❌ 文档错误:声称第8744行有
call_user_method检查需修复 - ✅ 实际情况:代码已修正,只检查PHP8支持的
call_user_func等函数 - 📝 结论:无需修改
- ❌ 文档错误:声称第8744行有
-
ntunnel_mysql.php误报:
- ❌ 文档错误:声称magic quotes函数需要兼容处理
- ✅ 实际情况:代码已有
phpversion_int() < 50400版本保护 - 📝 结论:PHP8环境下不会调用magic quotes函数,无需修改
-
修改范围大幅精简:
- 原声明:6-8个文件需修改
- 实际需求:仅4个文件需修改PHP8兼容性问题
🎯 真正需要修改的PHP8兼容性问题:
必须修改的4个文件:
-
transfer.php:
- mcrypt扩展替换(4个方法)
- 字符串大括号访问语法(1处)
-
Router.php:
- each()函数替换(1处)
-
common.inc.php:
- create_function()替换(2处)
-
global.func.php:
- 微信AES加密mcrypt替换(2个函数)
必须保持的原有"特征":
- 编码格式不匹配:encrypt()返回Hex,decrypt()期望Base64
- 自定义双重填充:非标准的pkcs_pad实现必须保持
- 去填充逻辑差异:不同方法的去填充处理必须保持原有差异
🚀 升级风险评估:
- 风险等级:从中高风险降低为中低风险
- 修改范围:大幅缩小,仅涉及真正的PHP8不兼容问题
- 功能一致性:所有原有特征(包括设计缺陷)都将严格保持
- 测试重点:4个文件的加密、遍历、动态函数功能验证
✅ 最终结论:经过深度重新审查,PHP8升级的实际修改范围比文档原声明的小得多。仅需修改4个文件中的真正PHP8兼容性问题,所有原有功能特征(包括编码格式不匹配等设计问题)都将严格保持,确保升级后功能与原系统100%一致。这种精确的修改范围大幅降低了升级风险,提高了成功率。
🚨 深度审查发现的关键功能一致性问题(v4.7补充)
⚠️ 重大发现:去填充逻辑的差异化实现
通过重新审查实际代码,发现transfer.php中存在两种不同的去填充逻辑,这是原设计的功能差异,必须严格保持:
1. mcryptDecrypt的简单去填充(AES方法)
// mcryptDecrypt中的实现(AES-128-ECB)
$dec_s = strlen($decrypted);
$padding = ord($decrypted[$dec_s-1]); // 直接取最后一个字节
$decrypted = substr($decrypted, 0, -$padding);
2. pkcs_unpad的完整PKCS验证(3DES方法)
// 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字符串大括号语法(必须修改)
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配对
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
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()保持简单去填充
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;
}
🎯 关键原则重申
- 保持填充算法差异:不同方法使用不同的去填充逻辑是原设计特征
- 严格配对关系:
- Crypt3Des方法 ↔ pkcs5_unpad(完整验证)
- AesCrypt方法 ↔ 简单去填充(无验证)
- 只修改语法问题:仅将
$data{}改为$data[],不改变任何逻辑
⚠️ 功能验证要求
// 测试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方法验证:
// 验证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()替换验证:
// 验证文件遍历行为完全一致
$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()替换验证:
// 验证动态类实例创建功能一致性
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加密验证:
// 验证微信协议兼容性
$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%保持