20) { // PKCS#8格式的特征:包含OID标识 (0x06 0x09 0x2a 0x86 0x48 0x86 0xf7 0x0d 0x01 0x01 0x01) // 简单判断:检查是否包含 "0609" (DER编码的OID标识) $hex = bin2hex(substr($decoded, 0, 30)); if (strpos($hex, '06092a864886f70d010101') !== false) { $isPkcs8 = true; } } // 格式化为PEM $keyContent = rtrim(chunk_split($privateKey, 64, "\n"), "\n"); if ($isPkcs8) { // PKCS#8格式 (建行使用的格式) $pem = "-----BEGIN PRIVATE KEY-----\n"; $pem .= $keyContent . "\n"; $pem .= "-----END PRIVATE KEY-----\n"; } else { // PKCS#1格式 (传统格式) $pem = "-----BEGIN RSA PRIVATE KEY-----\n"; $pem .= $keyContent . "\n"; $pem .= "-----END RSA PRIVATE KEY-----\n"; } return $pem; } /** * 生成RSA密钥对(用于测试) * * @param int $bits 密钥长度,默认1024 * @return array 包含public_key和private_key的数组 * @throws \Exception */ public static function generateKeyPair($bits = 1024) { $config = [ 'private_key_bits' => $bits, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]; // 生成密钥对 $resource = openssl_pkey_new($config); if (!$resource) { throw new \Exception('生成密钥对失败: ' . openssl_error_string()); } // 导出私钥 openssl_pkey_export($resource, $privateKey); // 获取公钥 $details = openssl_pkey_get_details($resource); $publicKey = $details['key']; // 转换为BASE64格式(去除PEM头尾) $privateKey = str_replace(['-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----', "\n"], '', $privateKey); $publicKey = str_replace(['-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----', "\n"], '', $publicKey); return [ 'public_key' => $publicKey, 'private_key' => $privateKey ]; } /** * RSA公钥验签(用于建行回调通知) * * 建行回调通知中的 SIGN 字段是使用商户私钥签名的, * 服务方需要使用建行公钥进行验签 * * @param string $data 待验签的原始数据 * @param string $signature 签名字符串(十六进制) * @param string $publicKey 建行公钥(BASE64编码) * @return bool 验签是否成功 * @throws \Exception */ public static function verify($data, $signature, $publicKey) { // 格式化公钥 $publicKey = self::formatPublicKey($publicKey); // 加载公钥资源 $pubKey = openssl_pkey_get_public($publicKey); if (!$pubKey) { throw new \Exception('公钥格式错误: ' . openssl_error_string()); } // 将十六进制签名转换为二进制 $signatureBinary = hex2bin($signature); if ($signatureBinary === false) { throw new \Exception('签名格式错误:无法从十六进制转换'); } // 使用公钥验签(SHA256算法) $result = openssl_verify($data, $signatureBinary, $pubKey, OPENSSL_ALGO_SHA256); // PHP 8+ 资源自动释放 if (PHP_VERSION_ID < 80000) { openssl_free_key($pubKey); } if ($result === 1) { return true; // 验签成功 } elseif ($result === 0) { return false; // 验签失败 } else { throw new \Exception('验签过程出错: ' . openssl_error_string()); } } /** * 建行通知验签(针对回调通知) * * 用于验证建行支付通知和退款通知的签名 * * @param array $params 通知参数(不包含SIGN字段) * @param string $signature SIGN字段的值 * @param string $ccbPublicKey 建行公钥 * @return bool 验签是否成功 * @throws \Exception */ public static function verifyNotify($params, $signature, $ccbPublicKey) { // 移除 SIGN 字段(如果存在) unset($params['SIGN']); // 按照建行规范拼接验签字符串 // 格式:将参数按字典序排列后拼接(key=value&key=value) ksort($params); $signStr = ''; foreach ($params as $key => $value) { if ($value !== '' && $value !== null) { $signStr .= $key . '=' . $value . '&'; } } $signStr = rtrim($signStr, '&'); // 调用验签方法 return self::verify($signStr, $signature, $ccbPublicKey); } }