diff --git a/addons/shopro/config/ccblife.php b/addons/shopro/config/ccblife.php index 2989577..34e095b 100644 --- a/addons/shopro/config/ccblife.php +++ b/addons/shopro/config/ccblife.php @@ -13,6 +13,13 @@ use think\Env; +// 读取 .env 文件以获取密钥配置 +$envFile = __DIR__ . '/../../../.env'; +$envVars = []; +if (file_exists($envFile)) { + $envVars = parse_ini_file($envFile); +} + return [ // API基础地址 (生产环境) 'api_base_url' => 'https://yunbusiness.ccb.com/tp_service/txCtrl/server', @@ -37,13 +44,8 @@ return [ 'branch_id' => Env::get('ccb.branch_id', '340650000'), // 密钥配置 (从.env读取,BASE64格式,不含PEM头尾) - // ⚠️ 注意:密钥会在代码中自动添加PEM包装,.env中只需要存储BASE64内容 - 'private_key' => Env::get('ccb.private_key', 'MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALrJmPmtQfP6mURtMxLEXqJHLldN3zYukoaRxG0lw2IdcC86H9C9brFz4YlJ+98z2mdELJaQWu8VWI4actSuPKgHTBr9MSpaii0QQpdINpwXJD9AglIrT7MxhMLYx3qAYDhjKUlC5hnWVYOg4sG32k/3dCebRHY8RDlrXUfHB2+VAgMBAAECgYArgn5R2pv8WymMmOtGudtZbb9LsuYF1v9mvVnGGv/SQQ060w1KMHYye83TjxpOueNsHqNMR0AHZS+Fmn+ZLyUNj9S77oQvUx5HQvY2/TDnsKbETzEMDybIWB+XdLsUkOrB3peVLTbk25i6oSNPOT2Fvd8TWbDqzBL9Ci27uJH72QJBAP/DfDLYoYx9OIRCykkxrDdQVFEkzhXj0wIkLa0Wnf8kP/JfBqvr0AGUPF8nEfh7fLVXYQlh5ab2FL5KvUifSL8CQQC69crW0fryyDHePp6OIVRUbw0T93h52vbGXnoQ6wdvKxZeL3MsfdNUvsJYeSxmtyY+LLgz1p3qOoEn6UpLvCirAkEA4N7qUvY+y3vJdhgXLNV8mkGJcLKQc5SUkJxogHeTQKGJi7ra7ctuXgUMM4jxduxz0CjcS1iEhxBzWn/x/mj1lwJBALgtv39VKLTXx1i7s5Ms/liXdfi/iC3zKbxOAk58WryHY+exMvMXmYMY0Xg7FySxNLl3cJeQy8ydifL5fbmSSTUCQQCj/YUbcTP8BQ6N0AgFdBwmXJyiNkB9zaDI5cEtpSCgq72m8lfn883GJ1MT7nKVXeX69/q5yDQUYiYPBXH4lCEC'), - 'public_key' => Env::get('ccb.public_key', 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6yZj5rUHz+plEbTMSxF6iRy5XTd82LpKGkcRtJcNiHXAvOh/QvW6xc+GJSfvfM9pnRCyWkFrvFViOGnLUrjyoB0wa/TEqWootEEKXSDacFyQ/QIJSK0+zMYTC2Md6gGA4YylJQuYZ1lWDoOLBt9pP93Qnm0R2PEQ5a11HxwdvlQIDAQAB'), - - // 建行平台公钥(如果建行提供则配置,否则使用merchant_public_key) - 'platform_public_key' => Env::get('ccb.platform_public_key', ''), - + 'private_key' => $envVars['private_key'] ?? '', + 'public_key' => $envVars['public_key'] ?? '', // HTTP请求配置 'http' => [ 'timeout' => 30, // 超时时间(秒) diff --git a/addons/shopro/controller/Ccblife.php b/addons/shopro/controller/Ccblife.php index ea6be9b..9c26d8a 100644 --- a/addons/shopro/controller/Ccblife.php +++ b/addons/shopro/controller/Ccblife.php @@ -27,53 +27,83 @@ class Ccblife extends Common protected $noNeedRight = ['*']; /** - * 建行生活用户登录(URL跳转方式) + * 建行生活用户登录(URL跳转方式或POST方式) * 建行App会携带加密参数跳转到此地址 * - * GET /addons/shopro/ccblife/login + * GET/POST /addons/shopro/ccblife/login */ public function login() { try { - // 获取URL参数 - $ccbParamSJ = $this->request->get('ccbParamSJ', ''); - $otherParams = $this->request->get(); + // 获取参数(支持 GET 和 POST) + $ccbParamSJ = $this->request->param('ccbParamSJ', ''); + $otherParams = $this->request->param(); // 验证必要参数 if (empty($ccbParamSJ)) { $this->error('缺少必要参数'); } - // 获取配置 - $config = config('ccblife'); + // 从插件配置文件直接加载(避免config()缓存问题) + $configFile = __DIR__ . '/../config/ccblife.php'; + if (!file_exists($configFile)) { + $this->error('建行配置文件不存在'); + } + + $config = include $configFile; + + // 验证配置 + if (empty($config['private_key'])) { + $this->error('配置错误:private_key 为空'); + } + + Log::info('建行登录解密参数: ' . json_encode([ + 'ccbParamSJ_length' => strlen($ccbParamSJ), + 'service_id' => $config['service_id'], + 'private_key_length' => strlen($config['private_key']) + ], JSON_UNESCAPED_UNICODE)); // 解密参数(使用服务方私钥) $decryptedParams = CcbUrlDecrypt::decrypt($ccbParamSJ, $config['private_key']); if (!$decryptedParams) { - $this->error('参数解密失败'); + // 检查日志文件获取详细错误 + $logFile = RUNTIME_PATH . 'log/' . date('Ym') . '/' . date('d') . '.log'; + $errorDetail = ''; + if (file_exists($logFile)) { + $logContent = file_get_contents($logFile); + // 提取最后几行日志 + $lines = explode("\n", $logContent); + $errorDetail = implode("\n", array_slice($lines, -10)); + } + + $this->error('参数解密失败,请查看日志: ' . $errorDetail); } - // 合并所有参数 - $allParams = array_merge($otherParams, $decryptedParams); + // 验证解密结果 + if (!is_array($decryptedParams)) { + $this->error('解密参数格式错误'); + } - // 获取建行用户信息 - $ccbUserId = $allParams['userid'] ?? ''; - $mobile = $allParams['mobile'] ?? ''; - $openId = $allParams['openid'] ?? ''; + Log::info('解密参数成功: ' . json_encode($decryptedParams, JSON_UNESCAPED_UNICODE)); + + // 获取建行用户信息(直接从解密参数中获取) + $ccbUserId = $decryptedParams['userid'] ?? ''; + $mobile = $decryptedParams['mobile'] ?? ''; + $openId = $decryptedParams['openid'] ?? ''; if (empty($ccbUserId)) { $this->error('用户信息获取失败'); } // 处理用户登录/注册 - $userInfo = $this->processUserLogin($ccbUserId, $mobile, $openId, $allParams); + $userInfo = $this->processUserLogin($ccbUserId, $mobile, $openId, $decryptedParams); // 生成商城Token $this->auth->direct($userInfo['user_id']); $token = $this->auth->getToken(); // 构建跳转URL - $redirectUrl = $allParams['redirect_url'] ?? '/pages/index/index'; + $redirectUrl = $decryptedParams['redirect_url'] ?? '/pages/index/index'; $this->success('登录成功', [ 'token' => $token, @@ -83,6 +113,9 @@ class Ccblife extends Common } catch (\Exception $e) { Log::error('建行生活登录失败: ' . $e->getMessage()); + Log::error('错误文件: ' . $e->getFile()); + Log::error('错误行号: ' . $e->getLine()); + Log::error('错误堆栈: ' . $e->getTraceAsString()); $this->error($e->getMessage()); } } diff --git a/addons/shopro/library/ccblife/CcbUrlDecrypt.php b/addons/shopro/library/ccblife/CcbUrlDecrypt.php index 68bd92d..b71b95a 100644 --- a/addons/shopro/library/ccblife/CcbUrlDecrypt.php +++ b/addons/shopro/library/ccblife/CcbUrlDecrypt.php @@ -29,8 +29,8 @@ class CcbUrlDecrypt { try { // 调试日志 - trace('开始解密建行参数(双重BASE64 + RSA)', 'info'); - trace('ccbParamSJ 长度: ' . strlen($ccbParamSJ), 'info'); + self::log('开始解密建行参数(双重BASE64 + RSA)', 'info'); + self::log('ccbParamSJ 长度: ' . strlen($ccbParamSJ), 'info'); // 验证输入 if (empty($ccbParamSJ)) { @@ -43,21 +43,21 @@ class CcbUrlDecrypt // 步骤1: URLDecode(如果还没有解码) $urlDecoded = urldecode($ccbParamSJ); - trace('URLDecode后长度: ' . strlen($urlDecoded), 'info'); + self::log('URLDecode后长度: ' . strlen($urlDecoded), 'info'); // 步骤2: 第一次 BASE64 解码 $firstDecode = base64_decode($urlDecoded); if ($firstDecode === false || empty($firstDecode)) { throw new \Exception('第一次 BASE64 解码失败'); } - trace('第一次BASE64解码成功,长度: ' . strlen($firstDecode), 'info'); + self::log('第一次BASE64解码成功,长度: ' . strlen($firstDecode), 'info'); // 步骤3: 第二次 BASE64 解码(建行特殊之处:双重BASE64编码) $secondDecode = base64_decode($firstDecode); if ($secondDecode === false || empty($secondDecode)) { throw new \Exception('第二次 BASE64 解码失败'); } - trace('第二次BASE64解码成功,长度: ' . strlen($secondDecode), 'info'); + self::log('第二次BASE64解码成功,长度: ' . strlen($secondDecode), 'info'); // 步骤4: RSA 私钥解密(直接处理二进制数据,不再用 CcbRSA::decrypt) $decrypted = self::rsaPrivateDecrypt($secondDecode, $privateKey); @@ -65,24 +65,30 @@ class CcbUrlDecrypt throw new \Exception('RSA解密失败'); } - trace('RSA解密成功,长度: ' . strlen($decrypted), 'info'); - trace('解密内容: ' . $decrypted, 'info'); + self::log('RSA解密成功,长度: ' . strlen($decrypted), 'info'); + self::log('解密内容: ' . $decrypted, 'info'); // 步骤5: 解析参数字符串为数组 + $params = []; parse_str($decrypted, $params); if (empty($params)) { throw new \Exception('解析参数失败,结果为空'); } - trace('参数解析成功: ' . json_encode($params, JSON_UNESCAPED_UNICODE), 'info'); + // 验证解析结果是否为有效数组 + if (!is_array($params)) { + throw new \Exception('解析参数失败,结果不是数组: ' . gettype($params)); + } + + self::log('参数解析成功: ' . json_encode($params, JSON_UNESCAPED_UNICODE), 'info'); return $params; } catch (\Exception $e) { // 记录错误日志 - trace('建行URL参数解密失败: ' . $e->getMessage(), 'error'); - trace('错误堆栈: ' . $e->getTraceAsString(), 'error'); + self::log('建行URL参数解密失败: ' . $e->getMessage(), 'error'); + self::log('错误堆栈: ' . $e->getTraceAsString(), 'error'); return false; } } @@ -97,6 +103,11 @@ class CcbUrlDecrypt private static function rsaPrivateDecrypt($encryptedData, $privateKey) { try { + self::log('rsaPrivateDecrypt 开始', 'info'); + self::log('加密数据长度: ' . strlen($encryptedData), 'info'); + self::log('加密数据MD5: ' . md5($encryptedData), 'info'); + self::log('私钥长度: ' . strlen($privateKey), 'info'); + // 格式化私钥为 PEM 格式 $pemPrivateKey = $privateKey; if (strpos($pemPrivateKey, '-----BEGIN') === false) { @@ -109,11 +120,23 @@ class CcbUrlDecrypt // 加载私钥资源 $priKey = openssl_pkey_get_private($pemPrivateKey); if (!$priKey) { - throw new \Exception('私钥格式错误: ' . openssl_error_string()); + $openssl_error = openssl_error_string(); + self::log('openssl_pkey_get_private 失败', 'error'); + self::log('OpenSSL错误: ' . ($openssl_error ?: '无错误信息'), 'error'); + self::log('PEM私钥前200字符: ' . substr($pemPrivateKey, 0, 200), 'error'); + throw new \Exception('私钥格式错误: ' . ($openssl_error ?: '未知错误')); } // 获取密钥大小 $keyDetails = openssl_pkey_get_details($priKey); + if (!$keyDetails || !isset($keyDetails['bits'])) { + self::log('openssl_pkey_get_details 失败', 'error'); + self::log('priKey 类型: ' . gettype($priKey), 'error'); + throw new \Exception('无法获取密钥详情'); + } + + self::log('密钥加载成功,大小: ' . ($keyDetails['bits'] / 8) . ' 字节', 'info'); + $keySize = $keyDetails['bits'] / 8; // 1024位 = 128字节 // 分块解密 @@ -129,12 +152,15 @@ class CcbUrlDecrypt $decrypted .= $decryptedBlock; } - openssl_free_key($priKey); + // PHP 8+ openssl_free_key() 已废弃,资源会自动释放 + if (PHP_VERSION_ID < 80000) { + openssl_free_key($priKey); + } return $decrypted; } catch (\Exception $e) { - trace('RSA私钥解密错误: ' . $e->getMessage(), 'error'); + self::log('RSA私钥解密错误: ' . $e->getMessage(), 'error'); return false; } } @@ -153,7 +179,7 @@ class CcbUrlDecrypt { // 确保密钥长度为8字节 if (strlen($key) !== 8) { - trace('DES密钥长度必须为8字节,当前: ' . strlen($key), 'error'); + self::log('DES密钥长度必须为8字节,当前: ' . strlen($key), 'error'); return false; } @@ -169,12 +195,12 @@ class CcbUrlDecrypt // 移除PKCS5填充 $result = self::removePKCS5Padding($decrypted); if ($result !== false) { - trace('使用phpseclib解密成功', 'info'); + self::log('使用phpseclib解密成功', 'info'); return $result; } } } catch (\Exception $e) { - trace('phpseclib解密失败: ' . $e->getMessage(), 'error'); + self::log('phpseclib解密失败: ' . $e->getMessage(), 'error'); } } @@ -191,15 +217,15 @@ class CcbUrlDecrypt // 移除PKCS5填充 $result = self::removePKCS5Padding($decrypted); if ($result !== false) { - trace('使用OpenSSL解密成功', 'info'); + self::log('使用OpenSSL解密成功', 'info'); return $result; } } } catch (\Exception $e) { - trace('OpenSSL解密失败: ' . $e->getMessage(), 'error'); + self::log('OpenSSL解密失败: ' . $e->getMessage(), 'error'); } - trace('所有DES解密方法都失败', 'error'); + self::log('所有DES解密方法都失败', 'error'); return false; } @@ -357,4 +383,31 @@ class CcbUrlDecrypt $separator = strpos($baseUrl, '?') === false ? '?' : '&'; return $baseUrl . $separator . $queryString; } + + /** + * 日志记录辅助方法 + * 兼容 trace() 函数和独立使用 + * + * @param string $message 日志消息 + * @param string $level 日志级别 + * @return void + */ + private static function log($message, $level = 'info') + { + // 如果 trace() 函数存在,使用它 + if (function_exists('trace')) { + trace($message, $level); + return; + } + + // 否则输出到标准错误流(CLI模式)和 error_log + $prefix = strtoupper($level); + $logMessage = "[{$prefix}] {$message}"; + + if (PHP_SAPI === 'cli') { + fwrite(STDERR, $logMessage . PHP_EOL); + } + + error_log($logMessage); + } } \ No newline at end of file diff --git a/addons/shopro/test/diagnose_ccb_encryption.php b/addons/shopro/test/diagnose_ccb_encryption.php new file mode 100644 index 0000000..e56003f --- /dev/null +++ b/addons/shopro/test/diagnose_ccb_encryption.php @@ -0,0 +1,152 @@ +getMessage() . "\n\n"; + exit; +} + +// 建行的加密参数 +$ccbParamSJ = 'cURYdUJxQ2pJTlVWQUltSW9DQWN1T2xJeFI4WjR2ZVY0NENTVmxPQW9RUVJ1bnJ2Ync5MkwzS2dXdDF1TTVSd3NvUFlsZWFURlYvK243aFNGaWRNeTJsZXRxM1VJR2k0VFdqNnU0Z2JXRnVxMnVIcjhzSVcrRW4xOGxaWDA4Snk0VW1hcFllZ05pekxmc2h6dGFCOTloTUtnVXh2ekJNbDFXZzdwT3lOcDBHZGx5N0FidW1FY1NudmlhNGcxZFAwM09BRk5sUE9USkM4QlJNNWtGTzRKVjFqTWx6RlhrblNwWG0rYmxpS2kyOFpYLy91OGZCR1NPNGp2dHN6b3N1TGxNZnUzd2JNeVUyOVpFTzYxWDRyNm0yb2c0ekZCQWpjc1VLdU9Kcmh1VjVwVDdQZlBlQ0MwRWhLQXFWWXBOdi9uS0FQNkZkZ2JER0ljN2tlUXNHdlFMTWlDeU9rVzdFU2phdG5jYjhZOUNaWkVEb29OdGVLdmlscXg4eHRFS1VtWHljU2ZNeUlDbk5yRW5nR3VXS1AvN3NacTNISE5BWSt2azBHdU1IaG5UWkl6bTdXUmhVYnNRclRYb0ljNy9UTnZVT3lmSVFIcTZ0KzZpZGdtc1NDejBkajVsSjkzVXNSM0NWZExCUk85NHQxZGNEOWorUnFhV2hrWmpiYUE4SlY='; + +echo "3. 尝试不同的解密方法\n\n"; + +// 方法1: 标准RSA解密(用商户私钥) +echo "方法1: 使用商户私钥解密(标准方式)\n"; +try { + $urlDecoded = urldecode($ccbParamSJ); + $decrypted = CcbRSA::decrypt($urlDecoded, $privateKey); + + echo " 解密成功,长度: " . strlen($decrypted) . "\n"; + + // 检查是否是可打印字符 + $isPrintable = ctype_print($decrypted); + echo " 是否为可打印字符: " . ($isPrintable ? '是' : '否') . "\n"; + + if ($isPrintable) { + echo " 解密内容: {$decrypted}\n"; + parse_str($decrypted, $params); + if (!empty($params)) { + echo " ✅ 解析成功!参数:\n"; + foreach ($params as $k => $v) { + echo " {$k} = {$v}\n"; + } + } + } else { + echo " 内容预览(十六进制): " . bin2hex(substr($decrypted, 0, 50)) . "...\n"; + echo " ❌ 解密结果不是有效文本\n"; + } +} catch (\Exception $e) { + echo " ❌ 解密失败: " . $e->getMessage() . "\n"; +} + +echo "\n"; + +// 方法2: 尝试用公钥解密(建行可能用建行私钥加密) +echo "方法2: 尝试用商户公钥解密(反向,测试建行是否用私钥加密)\n"; +try { + $urlDecoded = urldecode($ccbParamSJ); + + // 格式化公钥为PEM + $pemPublicKey = "-----BEGIN PUBLIC KEY-----\n"; + $pemPublicKey .= chunk_split($publicKey, 64, "\n"); + $pemPublicKey .= "-----END PUBLIC KEY-----\n"; + + $pubKeyResource = openssl_pkey_get_public($pemPublicKey); + if (!$pubKeyResource) { + throw new \Exception('公钥格式错误'); + } + + // BASE64解码 + $encryptedData = base64_decode($urlDecoded); + + // 获取密钥大小 + $keyDetails = openssl_pkey_get_details($pubKeyResource); + $keySize = $keyDetails['bits'] / 8; + + // 分块解密 + $dataBlocks = str_split($encryptedData, $keySize); + $decrypted = ''; + + foreach ($dataBlocks as $block) { + $decryptedBlock = ''; + // 尝试用公钥解密 + $success = @openssl_public_decrypt($block, $decryptedBlock, $pubKeyResource, OPENSSL_PKCS1_PADDING); + if (!$success) { + throw new \Exception('公钥解密失败'); + } + $decrypted .= $decryptedBlock; + } + + openssl_free_key($pubKeyResource); + + echo " 解密成功,长度: " . strlen($decrypted) . "\n"; + $isPrintable = ctype_print($decrypted); + echo " 是否为可打印字符: " . ($isPrintable ? '是' : '否') . "\n"; + + if ($isPrintable) { + echo " 解密内容: {$decrypted}\n"; + echo " ✅ 可能建行用的是建行私钥加密!\n"; + } else { + echo " ❌ 解密结果不是有效文本\n"; + } +} catch (\Exception $e) { + echo " ❌ 解密失败: " . $e->getMessage() . "\n"; +} + +echo "\n"; + +// 方法3: 检查是否需要建行公钥 +echo "方法3: 建议检查项\n"; +echo " - 确认建行后台配置的商户公钥是否为:\n"; +echo " " . substr($publicKey, 0, 50) . "...\n"; +echo " - 检查建行是否提供了建行平台公钥(用于解密建行发来的数据)\n"; +echo " - 联系建行技术支持确认ccbParamSJ的加密方式\n"; + +echo "\n========================================\n"; +echo "诊断完成\n"; +echo "========================================\n"; diff --git a/addons/shopro/test/test_ccb_correct_decrypt.php b/addons/shopro/test/test_ccb_correct_decrypt.php new file mode 100644 index 0000000..8db4306 --- /dev/null +++ b/addons/shopro/test/test_ccb_correct_decrypt.php @@ -0,0 +1,147 @@ + $block) { + $decryptedBlock = ''; + $success = openssl_private_decrypt($block, $decryptedBlock, $priKey, OPENSSL_PKCS1_PADDING); + if (!$success) { + throw new \Exception("解密第 " . ($i+1) . " 块失败: " . openssl_error_string()); + } + $decrypted .= $decryptedBlock; + } + + openssl_free_key($priKey); + + echo " 解密成功!\n"; + echo " 解密后长度: " . strlen($decrypted) . "\n\n"; + + // 步骤5: 显示结果 + echo "步骤5: 解密结果\n"; + echo " 原始内容:\n"; + echo " ----------------------------------------\n"; + echo " {$decrypted}\n"; + echo " ----------------------------------------\n\n"; + + // 步骤6: 解析参数 + echo "步骤6: 解析 URL 参数\n"; + parse_str($decrypted, $params); + + if (empty($params)) { + echo " ⚠️ 解析为空,可能不是 URL 参数格式\n"; + } else { + echo " 解析成功!参数列表:\n"; + foreach ($params as $key => $value) { + echo " {$key} = {$value}\n"; + } + } + + echo "\n========================================\n"; + echo "✅ 解密成功!\n"; + echo "========================================\n"; + +} catch (\Exception $e) { + echo "\n========================================\n"; + echo "❌ 解密失败\n"; + echo "========================================\n"; + echo "错误: " . $e->getMessage() . "\n"; + echo "堆栈:\n" . $e->getTraceAsString() . "\n"; +} diff --git a/addons/shopro/test/test_rsa_decrypt.php b/addons/shopro/test/test_rsa_decrypt.php new file mode 100644 index 0000000..ea2a178 --- /dev/null +++ b/addons/shopro/test/test_rsa_decrypt.php @@ -0,0 +1,136 @@ + $value) { + echo " {$key} = {$value}\n"; + } + + echo "\n========================================\n"; + echo "✅ 解密成功!\n"; + echo "========================================\n"; + +} catch (\Exception $e) { + echo "\n========================================\n"; + echo "❌ 解密失败!\n"; + echo "========================================\n"; + echo "错误信息: " . $e->getMessage() . "\n"; + echo "错误行号: " . $e->getLine() . "\n"; + echo "错误文件: " . $e->getFile() . "\n"; + echo "\n堆栈跟踪:\n" . $e->getTraceAsString() . "\n"; + + // 额外诊断 + echo "\n========================================\n"; + echo "诊断信息\n"; + echo "========================================\n"; + + // 检查 BASE64 解码 + echo "\n1. BASE64 解码测试:\n"; + $base64Decoded = base64_decode($urlDecoded); + if ($base64Decoded === false) { + echo " ❌ BASE64 解码失败\n"; + } else { + echo " ✓ BASE64 解码成功\n"; + echo " 解码后长度: " . strlen($base64Decoded) . "\n"; + echo " 十六进制预览: " . bin2hex(substr($base64Decoded, 0, 32)) . "...\n"; + } + + // 检查私钥格式 + echo "\n2. 私钥格式测试:\n"; + try { + // 格式化私钥 + $formattedKey = $privateKey; + if (strpos($formattedKey, '-----BEGIN') === false) { + $formattedKey = preg_replace('/\s+/', '', $formattedKey); + $formattedKey = "-----BEGIN RSA PRIVATE KEY-----\n" . + chunk_split($formattedKey, 64, "\n") . + "-----END RSA PRIVATE KEY-----\n"; + } + + $keyResource = openssl_pkey_get_private($formattedKey); + if ($keyResource === false) { + echo " ❌ 私钥格式错误: " . openssl_error_string() . "\n"; + } else { + echo " ✓ 私钥格式正确\n"; + $keyDetails = openssl_pkey_get_details($keyResource); + echo " 密钥类型: " . ($keyDetails['type'] === OPENSSL_KEYTYPE_RSA ? 'RSA' : '未知') . "\n"; + echo " 密钥位数: " . $keyDetails['bits'] . "\n"; + openssl_free_key($keyResource); + } + } catch (\Exception $e2) { + echo " ❌ 私钥检查失败: " . $e2->getMessage() . "\n"; + } + + // 检查数据长度 + echo "\n3. 数据长度检查:\n"; + if (isset($base64Decoded)) { + $dataLen = strlen($base64Decoded); + echo " 加密数据长度: {$dataLen} 字节\n"; + + if (isset($keyDetails)) { + $keySize = $keyDetails['bits'] / 8; + $blockCount = $dataLen / $keySize; + echo " 密钥大小: {$keySize} 字节\n"; + echo " 分块数量: " . $blockCount . " (应该是整数)\n"; + + if ($blockCount != floor($blockCount)) { + echo " ⚠️ 警告: 数据长度不是密钥大小的整数倍\n"; + } + } + } +} diff --git a/addons/shopro/test/verify_keypair.php b/addons/shopro/test/verify_keypair.php new file mode 100644 index 0000000..0103c50 --- /dev/null +++ b/addons/shopro/test/verify_keypair.php @@ -0,0 +1,81 @@ +getMessage() . "\n\n"; +} + +// 显示公钥信息(用于确认是否提交给建行) +echo "3. 你的公钥(BASE64格式,提交给建行的应该是这个)\n"; +echo "----------------------------------------\n"; +echo $publicKey . "\n"; +echo "----------------------------------------\n\n"; + +echo "4. 你的公钥(PEM格式)\n"; +echo "----------------------------------------\n"; +$pemPublicKey = "-----BEGIN PUBLIC KEY-----\n"; +$pemPublicKey .= chunk_split($publicKey, 64, "\n"); +$pemPublicKey .= "-----END PUBLIC KEY-----\n"; +echo $pemPublicKey; +echo "----------------------------------------\n\n"; + +echo "⚠️ 重要提示:\n"; +echo "1. 请确认建行那边配置的公钥是否和上面显示的公钥一致\n"; +echo "2. 如果不一致,需要重新提交正确的公钥给建行\n"; +echo "3. 如果一致但仍然解密失败,可能是建行加密方式有问题\n";