mirror of
https://gitee.com/liuxioabin/fengketrade.git
synced 2026-04-17 21:03:17 +08:00
公钥
This commit is contained in:
parent
e86985e733
commit
f917f62010
@ -54,16 +54,27 @@ return [
|
|||||||
'private_key' => $envVars['private_key'] ?? '',
|
'private_key' => $envVars['private_key'] ?? '',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商户公钥 (商户自己生成的RSA公钥,对应上面的私钥)
|
* 服务方公钥 (服务方自己生成的RSA公钥)
|
||||||
* 用途:
|
* 用途:
|
||||||
* - 提交给建行用于验证商户签名
|
|
||||||
* - 支付下单的MD5签名计算(PLATFORMPUB字段)
|
* - 支付下单的MD5签名计算(PLATFORMPUB字段)
|
||||||
* - 加密商户公钥(ENCPUB字段)
|
* - 用于加密商户公钥生成ENCPUB
|
||||||
|
* 格式: BASE64格式(不含PEM头尾) 或 PEM格式(含头尾)
|
||||||
|
*
|
||||||
|
* 📌 注意: 这是参与支付签名计算的服务方公钥
|
||||||
|
*/
|
||||||
|
'public_key' => $envVars['public_key'] ?? '',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户公钥 (商户自己生成的RSA公钥,与上面的private_key对应)
|
||||||
|
* 用途:
|
||||||
|
* - 生成ENCPUB密文(使用服务方公钥加密商户公钥后30位)
|
||||||
|
* - 提交给建行用于验证商户签名
|
||||||
* 格式: BASE64格式(不含PEM头尾) 或 PEM格式(含头尾)
|
* 格式: BASE64格式(不含PEM头尾) 或 PEM格式(含头尾)
|
||||||
*
|
*
|
||||||
* 📌 注意: 需要将此公钥提交给建行进行配置
|
* 📌 注意: 需要将此公钥提交给建行进行配置
|
||||||
|
* 📌 如果已上架建行生活并同步公钥,可以不再上送ENCPUB
|
||||||
*/
|
*/
|
||||||
'public_key' => $envVars['public_key'] ?? '',
|
'merchant_public_key' => $envVars['merchant_public_key'] ?? ($envVars['public_key'] ?? ''),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 建行平台API公钥 (建行生活平台提供的RSA公钥)
|
* 建行平台API公钥 (建行生活平台提供的RSA公钥)
|
||||||
|
|||||||
@ -119,6 +119,16 @@ class CcbPaymentService
|
|||||||
$macParams['PROINFO'] = $proinfo;
|
$macParams['PROINFO'] = $proinfo;
|
||||||
|
|
||||||
$macParams['REFERER'] = ''; // 商户URL(空字符串)
|
$macParams['REFERER'] = ''; // 商户URL(空字符串)
|
||||||
|
|
||||||
|
// ⚠️ 关键修复:INSTALLNUM必须在THIRDAPPINFO之前(严格按照文档4.1表格顺序)
|
||||||
|
// 1.3 可选参数(按文档表格顺序,有值才参与MAC)
|
||||||
|
// 注意:根据文档4.2,橙色字段有值时才参与MAC,空值不参与
|
||||||
|
|
||||||
|
// 分期期数(在REFERER之后,THIRDAPPINFO之前)
|
||||||
|
if (!empty($this->config['install_num'])) {
|
||||||
|
$macParams['INSTALLNUM'] = $this->config['install_num'];
|
||||||
|
}
|
||||||
|
|
||||||
$macParams['THIRDAPPINFO'] = 'comccbpay1234567890cloudmerchant'; // 客户端标识(固定值)
|
$macParams['THIRDAPPINFO'] = 'comccbpay1234567890cloudmerchant'; // 客户端标识(固定值)
|
||||||
|
|
||||||
// 记录关键参数
|
// 记录关键参数
|
||||||
@ -126,14 +136,6 @@ class CcbPaymentService
|
|||||||
Log::info('[建行支付] 商户信息 merchant_id:' . ($macParams['MERCHANTID'] ?? 'N/A') . ' pos_id:' . ($macParams['POSID'] ?? 'N/A') . ' branch_id:' . ($macParams['BRANCHID'] ?? 'N/A'));
|
Log::info('[建行支付] 商户信息 merchant_id:' . ($macParams['MERCHANTID'] ?? 'N/A') . ' pos_id:' . ($macParams['POSID'] ?? 'N/A') . ' branch_id:' . ($macParams['BRANCHID'] ?? 'N/A'));
|
||||||
Log::info('[建行支付] 商品信息 proinfo:' . $proinfo);
|
Log::info('[建行支付] 商品信息 proinfo:' . $proinfo);
|
||||||
|
|
||||||
// 1.3 可选参数(按文档表格顺序,有值才参与MAC)
|
|
||||||
// ⚠️ 注意:根据文档4.2,橙色字段有值时才参与MAC,空值不参与
|
|
||||||
|
|
||||||
// 分期期数(在THIRDAPPINFO之后)
|
|
||||||
if (!empty($this->config['install_num'])) {
|
|
||||||
$macParams['INSTALLNUM'] = $this->config['install_num'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 超时时间
|
// 超时时间
|
||||||
if (!empty($this->config['timeout'])) {
|
if (!empty($this->config['timeout'])) {
|
||||||
$macParams['TIMEOUT'] = $this->config['timeout'];
|
$macParams['TIMEOUT'] = $this->config['timeout'];
|
||||||
@ -413,40 +415,95 @@ class CcbPaymentService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加密商户公钥后30位(用于支付串的ENCPUB字段)
|
* 生成ENCPUB(商户公钥密文)
|
||||||
*
|
*
|
||||||
* 根据建行文档v2.2规范:
|
* 根据建行文档4.4:
|
||||||
* "使用服务方公钥对商户公钥后30位进行RSA加密并base64后的密文"
|
* "使用服务方公钥对商户公钥后30位进行RSA加密,再进行base64后,生成的密文串"
|
||||||
|
*
|
||||||
|
* 注意:这里的"商户公钥后30位"是指RSA公钥模数(modulus)的十六进制表示的后30位
|
||||||
|
* 不是BASE64字符串的后30位!
|
||||||
*
|
*
|
||||||
* @return string BASE64编码的加密密文
|
* @return string BASE64编码的加密密文
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private function encryptPublicKeyLast30()
|
private function encryptPublicKeyLast30()
|
||||||
{
|
{
|
||||||
$publicKey = $this->config['public_key'] ?? '';
|
// 获取商户公钥(被加密的公钥)
|
||||||
|
$merchantPublicKey = $this->config['merchant_public_key'] ?? '';
|
||||||
|
|
||||||
if (empty($publicKey)) {
|
// 获取服务方公钥(用于加密的公钥)
|
||||||
|
$servicePublicKey = $this->config['public_key'] ?? '';
|
||||||
|
|
||||||
|
if (empty($merchantPublicKey)) {
|
||||||
|
throw new \Exception('商户公钥未配置');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($servicePublicKey)) {
|
||||||
throw new \Exception('服务方公钥未配置');
|
throw new \Exception('服务方公钥未配置');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 去除 PEM 格式的头尾和空白字符,获取纯 BASE64 内容
|
// ✅ 关键修复:提取商户公钥模数的十六进制表示的后30位
|
||||||
$publicKeyContent = str_replace([
|
try {
|
||||||
'-----BEGIN PUBLIC KEY-----',
|
// 格式化商户公钥为PEM格式
|
||||||
'-----END PUBLIC KEY-----',
|
$merchantPemKey = $this->formatPublicKeyToPem($merchantPublicKey);
|
||||||
'-----BEGIN RSA PUBLIC KEY-----',
|
|
||||||
'-----END RSA PUBLIC KEY-----',
|
|
||||||
"\r", "\n", " ", "\t"
|
|
||||||
], '', $publicKey);
|
|
||||||
|
|
||||||
// 取后30位
|
// 解析商户公钥,提取模数(modulus)
|
||||||
$last30Chars = substr($publicKeyContent, -30);
|
$keyResource = openssl_pkey_get_public($merchantPemKey);
|
||||||
|
if (!$keyResource) {
|
||||||
|
throw new \Exception('商户公钥格式错误: ' . openssl_error_string());
|
||||||
|
}
|
||||||
|
|
||||||
if (strlen($last30Chars) < 30) {
|
$keyDetails = openssl_pkey_get_details($keyResource);
|
||||||
throw new \Exception('商户公钥长度不足30位');
|
openssl_free_key($keyResource);
|
||||||
|
|
||||||
|
if (!isset($keyDetails['rsa']['n'])) {
|
||||||
|
throw new \Exception('无法提取商户公钥RSA模数');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将模数转换为十六进制字符串(去掉前导0x)
|
||||||
|
$modulusHex = bin2hex($keyDetails['rsa']['n']);
|
||||||
|
|
||||||
|
// 取后30位十六进制字符
|
||||||
|
$last30Chars = substr($modulusHex, -30);
|
||||||
|
|
||||||
|
if (strlen($last30Chars) < 30) {
|
||||||
|
throw new \Exception('商户公钥模数长度不足30位');
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('[建行支付] 商户公钥模数后30位: ' . $last30Chars);
|
||||||
|
|
||||||
|
// 使用服务方公钥加密这30位十六进制字符串
|
||||||
|
// CcbRSA::encrypt 会自动进行base64编码
|
||||||
|
return CcbRSA::encrypt($last30Chars, $servicePublicKey);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('[建行支付] ENCPUB生成失败: ' . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化公钥为PEM格式
|
||||||
|
*
|
||||||
|
* @param string $publicKey BASE64或PEM格式的公钥
|
||||||
|
* @return string PEM格式的公钥
|
||||||
|
*/
|
||||||
|
private function formatPublicKeyToPem($publicKey)
|
||||||
|
{
|
||||||
|
// 移除可能存在的空格和换行
|
||||||
|
$publicKey = preg_replace('/\s+/', '', $publicKey);
|
||||||
|
|
||||||
|
// 如果已经是PEM格式,直接返回
|
||||||
|
if (strpos($publicKey, '-----BEGIN PUBLIC KEY-----') !== false) {
|
||||||
|
return $publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 使用 CcbRSA 类进行加密(统一密钥格式化逻辑)
|
// 转换为PEM格式
|
||||||
return CcbRSA::encrypt($last30Chars, $publicKey);
|
$pem = "-----BEGIN PUBLIC KEY-----\n";
|
||||||
|
$pem .= rtrim(chunk_split($publicKey, 64, "\n"), "\n") . "\n";
|
||||||
|
$pem .= "-----END PUBLIC KEY-----\n";
|
||||||
|
|
||||||
|
return $pem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user