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'] ?? '',
|
||||
|
||||
/**
|
||||
* 商户公钥 (商户自己生成的RSA公钥,对应上面的私钥)
|
||||
* 服务方公钥 (服务方自己生成的RSA公钥)
|
||||
* 用途:
|
||||
* - 提交给建行用于验证商户签名
|
||||
* - 支付下单的MD5签名计算(PLATFORMPUB字段)
|
||||
* - 加密商户公钥(ENCPUB字段)
|
||||
* - 用于加密商户公钥生成ENCPUB
|
||||
* 格式: BASE64格式(不含PEM头尾) 或 PEM格式(含头尾)
|
||||
*
|
||||
* 📌 注意: 这是参与支付签名计算的服务方公钥
|
||||
*/
|
||||
'public_key' => $envVars['public_key'] ?? '',
|
||||
|
||||
/**
|
||||
* 商户公钥 (商户自己生成的RSA公钥,与上面的private_key对应)
|
||||
* 用途:
|
||||
* - 生成ENCPUB密文(使用服务方公钥加密商户公钥后30位)
|
||||
* - 提交给建行用于验证商户签名
|
||||
* 格式: BASE64格式(不含PEM头尾) 或 PEM格式(含头尾)
|
||||
*
|
||||
* 📌 注意: 需要将此公钥提交给建行进行配置
|
||||
* 📌 如果已上架建行生活并同步公钥,可以不再上送ENCPUB
|
||||
*/
|
||||
'public_key' => $envVars['public_key'] ?? '',
|
||||
'merchant_public_key' => $envVars['merchant_public_key'] ?? ($envVars['public_key'] ?? ''),
|
||||
|
||||
/**
|
||||
* 建行平台API公钥 (建行生活平台提供的RSA公钥)
|
||||
|
||||
@ -119,6 +119,16 @@ class CcbPaymentService
|
||||
$macParams['PROINFO'] = $proinfo;
|
||||
|
||||
$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'; // 客户端标识(固定值)
|
||||
|
||||
// 记录关键参数
|
||||
@ -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('[建行支付] 商品信息 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'])) {
|
||||
$macParams['TIMEOUT'] = $this->config['timeout'];
|
||||
@ -413,40 +415,95 @@ class CcbPaymentService
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密商户公钥后30位(用于支付串的ENCPUB字段)
|
||||
* 生成ENCPUB(商户公钥密文)
|
||||
*
|
||||
* 根据建行文档v2.2规范:
|
||||
* "使用服务方公钥对商户公钥后30位进行RSA加密并base64后的密文"
|
||||
* 根据建行文档4.4:
|
||||
* "使用服务方公钥对商户公钥后30位进行RSA加密,再进行base64后,生成的密文串"
|
||||
*
|
||||
* 注意:这里的"商户公钥后30位"是指RSA公钥模数(modulus)的十六进制表示的后30位
|
||||
* 不是BASE64字符串的后30位!
|
||||
*
|
||||
* @return string BASE64编码的加密密文
|
||||
* @throws \Exception
|
||||
*/
|
||||
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('服务方公钥未配置');
|
||||
}
|
||||
|
||||
// 去除 PEM 格式的头尾和空白字符,获取纯 BASE64 内容
|
||||
$publicKeyContent = str_replace([
|
||||
'-----BEGIN PUBLIC KEY-----',
|
||||
'-----END PUBLIC KEY-----',
|
||||
'-----BEGIN RSA PUBLIC KEY-----',
|
||||
'-----END RSA PUBLIC KEY-----',
|
||||
"\r", "\n", " ", "\t"
|
||||
], '', $publicKey);
|
||||
// ✅ 关键修复:提取商户公钥模数的十六进制表示的后30位
|
||||
try {
|
||||
// 格式化商户公钥为PEM格式
|
||||
$merchantPemKey = $this->formatPublicKeyToPem($merchantPublicKey);
|
||||
|
||||
// 取后30位
|
||||
$last30Chars = substr($publicKeyContent, -30);
|
||||
|
||||
if (strlen($last30Chars) < 30) {
|
||||
throw new \Exception('商户公钥长度不足30位');
|
||||
// 解析商户公钥,提取模数(modulus)
|
||||
$keyResource = openssl_pkey_get_public($merchantPemKey);
|
||||
if (!$keyResource) {
|
||||
throw new \Exception('商户公钥格式错误: ' . openssl_error_string());
|
||||
}
|
||||
|
||||
// ✅ 使用 CcbRSA 类进行加密(统一密钥格式化逻辑)
|
||||
return CcbRSA::encrypt($last30Chars, $publicKey);
|
||||
$keyDetails = openssl_pkey_get_details($keyResource);
|
||||
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;
|
||||
}
|
||||
|
||||
// 转换为PEM格式
|
||||
$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