招商银行回调通知验签过不了。麻烦大佬们看下
1. 运行环境
windows10 .docker 中
1). 当前使用的 Laravel 版本?
laravel10
2). 当前使用的 php/php-fpm 版本?
PHP 版本: php8.2
php-fpm 版本:
3). 当前系统
Windows 10
4). 业务环境
开发环境
5). 相关软件版本
Nginx 、MySQL
2. 问题描述?
3. 您期望得到的结果?
4. 您实际得到的结果?
对接的招商银行的银企直连接口。
1、使用了 lpilp/guomi 的包
2、签名用的给的示例代码
签名是可以的,正常调用接口验签也是可以的。。但是用他的主动通知接口,回来的数据 验签就失败了。
## 招商银行回调通知验签过不了。群里的同类型都试了。麻烦大佬们看下 !!!
<?php
namespace App\Console\Commands;
use App\Outside\DcHelperOutside;
use FG\ASN1\ASNObject;
use Illuminate\Console\Command;
use Rtgm\sm\RtSm4;
use Mdanter\Ecc\Crypto\Key\PrivateKey;
use Mdanter\Ecc\Crypto\Signature\Signature;
use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;
use Rtgm\ecc\RtEccFactory;
use Rtgm\sm\RtSm2;
class Test1 extends Command
{
var $url;
// 企业网银用户号
var $uid;
// 国密算法向量,根据用户号生成
var $userId;
// 算法,固定为国密算法
var $alg;
// 客户私钥
var $privateKey;
// 银行公钥
var $publicKey;
// 协商的对称密钥
var $symKey;
/**
* The name and signature of the console command. * * @var string
*/
protected $signature = 'app:test1';
/**
* The console command description. * * @var string
*/
protected $description = 'Command description';
/**
* Execute the console command. */ public function handle()
{
// 测试地址,生产需要替换
$url = "http://cdctest.cmburl.cn:80/cdcserver/api/v2";
// 生产地址
// 银行公钥,生产需要替换
$publicKey = 'BNsIe9U0x8IeSe4h/dxUzVEz9pie0hDSfMRINRXc7s1UIXfkExnYECF4QqJ2SnHxLv3z/99gsfDQrQ6dzN5lZj0=';
$privateKey = 'NBtl7WnuUtA2v5FaebEkU0/Jj1IodLGT6lQqwkzmd2E=';
// 对称密钥,生产需要替换
$symKey = 'VuAzSWQhsoNqzn0K';
// 企业网银用户号,生产需要替换
$uid = "N003261207";
// 业务接口名,这里是查询业务模式接口,生产请替换为对应接口名
$funcode = "DCLISMOD";
// 准备接口数据,生产请替换为具体接口请求报文,包含所需的请求字段
$currentDatetime = date("YmdHis");
$reqid = date("YmdHisu") . rand(1000000, 9999999);
$data = '{"request":{"body":{"buscod":"N02030"},"head":{"funcode":"' . $funcode . '","userid":"' . $uid . '","reqid":"' . $reqid . '"}},"signature":{"sigdat":"__signature_sigdat__","sigtim":"' . $currentDatetime . '"}}';
$this->deal($url, $uid, $privateKey, $publicKey, $symKey);
$response = $this->sendRequest($data, $funcode);
echo '响应报文:<br/>' . $response;
}
function deal($url, $uid, $privateKey, $publicKey, $symKey)
{
$this->url = $url;
$this->uid = $uid;
$this->userId = sprintf('%-016s', $uid);
$this->alg = "SM";
$this->privateKey = unpack("H*", base64_decode($privateKey))[1];
$this->publicKey = unpack("H*", base64_decode($publicKey))[1];
$this->symKey = $symKey;
}
function sendRequest($data, $funcode)
{
// 对请求报文做排序
$toSortArray = json_decode($data, true);
$this->recursiveArraySort($toSortArray);
$data = json_encode($toSortArray, JSON_UNESCAPED_UNICODE);
// 生成签名
$sm2 = new RtSm2("base64");
$sign = $sm2->doSign($data, $this->privateKey, $this->userId);
// 处理签名
$sign = base64_decode($sign);
$point = ASNObject::fromBinary($sign)->getChildren();
$pointX = $this->formatHex($point[0]->getContent());
$pointY = $this->formatHex($point[1]->getContent());
$sign = $pointX . $pointY;
$sign = base64_encode(hex2bin($sign));
// 替换签名字段
$data = str_replace('__signature_sigdat__', $sign, $data);
// 对数据进行对称加密
$sm4 = new RtSm4($this->symKey);
$encryptData = $sm4->encrypt($data, 'sm4-cbc', $this->userId, "base64");
// 发送请求
$response = $this->httpPost($this->getFormData($encryptData, $funcode));
// 返回结果解密
$json = $sm4->decrypt($response, 'sm4-cbc', $this->userId, 'base64');
$respdata = json_decode($json, true);
// 验证签名是否正确
$sign = $respdata["signature"]["sigdat"];
// 将数据中的签名重置
$respdata["signature"]["sigdat"] = "__signature_sigdat__";
$json = str_replace($sign, "__signature_sigdat__", $json);
$signHex = bin2hex(base64_decode($sign));
$r = substr($signHex, 0, 64);
$s = substr($signHex, 64, 64);
$r = gmp_init($r, 16);
$s = gmp_init($s, 16);
$signature = new Signature($r, $s);
$serializer = new DerSignatureSerializer();
$serializedSig = $serializer->serialize($signature);
$sign = base64_encode($serializedSig);
$b = $sm2->verifySign($json, $sign, $this->publicKey, $this->userId);
if ($b === true) {
return $json;
} else {
throw new \Exception("响应报文的签名无效");
}
}
private function httpPost($data)
{
$urlInfo = parse_url($this->url);
foreach ($data as $key => $value)
$values[] = "$key=" . urlencode($value);
$dataString = implode("&", $values);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $dataString);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) {
throw new \Exception("Error in making the request: " . curl_error($ch));
}
if ($status != 200) {
echo "Status Code: " . $status . " Response: " . $response;die;
throw new \Exception("Status Code: " . $status . " Response: " . $response);
}
if (substr($response, 0, 10) === "CDCServer:") {
throw new \Exception("访问目标地址 " . $this->url . " 失败:" . $response);
}
return $response;
}
private function getFormData($data, $funcode)
{
return array('UID' => $this->uid, 'ALG' => $this->alg, 'DATA' => $data, 'FUNCODE' => $funcode);
}
private function formatHex($dec)
{
$hex = gmp_strval(gmp_init($dec, 10), 16);
$len = strlen($hex);
if ($len == 64) {
return $hex;
}
if ($len < 64) {
$hex = str_pad($hex, 64, "0", STR_PAD_LEFT);
} else {
$hex = substr($hex, $len - 64, 64);
}
return $hex;
}
private function recursiveArraySort(&$array)
{
ksort($array);
foreach ($array as &$value) {
if (is_array($value)) {
$this->recursiveArraySort($value);
}
}
}
}
这是回调通知数据。死活验签过不去。
{
"notdat": "{\"msgdat\":{\"backInfo\":{\"trsBrn\":\"755512\",\"busTyp\":\"0\",\"reqNbr\":\"8782029821\",\"sndBrn\":\"755512\",\"outTyp\":\"13\",\"rtnNar\":\"账号误\",\"sysFlg\":\"N\",\"narTxt\":\"xencio test\",\"crtBnk\":\"中国工商银行总行清算中心\",\"updDat\":\"20241025\",\"rcvEaa\":\"中国工商银行总行清算中心\",\"ctyFlg\":\"N\",\"dbtAcc\":\"128964320010000\",\"rcvTyp\":\"P\",\"crtNam\":\"吴某\",\"trsBbk\":\"755\",\"isuCnl\":\"ICO\",\"busLvl\":\"N\",\"feeAmt\":0,\"rtnCod\":\"999\",\"busSts\":\"R\",\"rcdSts\":\"A\",\"crtAcc\":\"4408808820\",\"sndClt\":\"1289643200\",\"isuDat\":\"20241025\",\"dbtNam\":\"银企直连测试用户专用06\",\"yurRef\":\"202410251330\",\"ccyNbr\":\"10\",\"trsAmt\":100,\"busNbr\":\"00S136ZS000001000203\"}},\"msgtyp\":\"FINB\"}",
"notkey": "128964320010000",
"notnbr": "258565792960086016",
"nottyp": "YQN02030",
"sigdat": "LYK2ZMp/oBXz7PMVR+mLvO7qMTza2f3i/g3PqW8Aoy1wj5ClbRZKclVGgHNodjHB3RO5k4L8rR3FBsotUvgObQ==",
"sigtim": "20241025133441",
"usrnbr": "U006566025"
}
大佬们有解决思路么???麻烦说下 。搞了1个多星期了。其他都解决了。就差回调通知验签了
这是招行的文档地址
openbiz.cmbchina.com/developer/UI/...
验签代码
$str = '{"notdat":"{\"msgdat\":{\"backInfo\":{\"trsBrn\":\"755512\",\"busTyp\":\"0\",\"reqNbr\":\"8782029821\",\"sndBrn\":\"755512\",\"outTyp\":\"13\",\"rtnNar\":\"账号误\",\"sysFlg\":\"N\",\"narTxt\":\"xencio test\",\"crtBnk\":\"中国工商银行总行清算中心\",\"updDat\":\"20241025\",\"rcvEaa\":\"中国工商银行总行清算中心\",\"ctyFlg\":\"N\",\"dbtAcc\":\"128964320010000\",\"rcvTyp\":\"P\",\"crtNam\":\"吴某\",\"trsBbk\":\"755\",\"isuCnl\":\"ICO\",\"busLvl\":\"N\",\"feeAmt\":0,\"rtnCod\":\"999\",\"busSts\":\"R\",\"rcdSts\":\"A\",\"crtAcc\":\"4408808820\",\"sndClt\":\"1289643200\",\"isuDat\":\"20241025\",\"dbtNam\":\"银企直连测试用户专用06\",\"yurRef\":\"202410251330\",\"ccyNbr\":\"10\",\"trsAmt\":100,\"busNbr\":\"00S136ZS000001000203\"}},\"msgtyp\":\"FINB\"}","notkey":"128964320010000","notnbr":"258565792960086016","nottyp":"YQN02030","sigdat":"LYK2ZMp/oBXz7PMVR+mLvO7qMTza2f3i/g3PqW8Aoy1wj5ClbRZKclVGgHNodjHB3RO5k4L8rR3FBsotUvgObQ==","sigtim":"20241025133441","usrnbr":"U006566025"}';
$data = json_decode($str, true)??[];
var_dump("LYK2ZMp/oBXz7PMVR+mLvO7qMTza2f3i/g3PqW8Aoy1wj5ClbRZKclVGgHNodjHB3RO5k4L8rR3FBsotUvgObQ==");
$sm2 = new RtSm2("base64");
var_dump("LYK2ZMp/oBXz7PMVR+mLvO7qMTza2f3i/g3PqW8Aoy1wj5ClbRZKclVGgHNodjHB3RO5k4L8rR3FBsotUvgObQ==");
$sm2 = new RtSm2("base64");
$signStr = $data['sigdat'];
$signHex = bin2hex(base64_decode($signStr));
$r = substr($signHex, 0, 64);
$s = substr($signHex, 64, 64);
$r = gmp_init($r, 16);
$s = gmp_init($s, 16);
$signature = new Signature($r, $s);
$serializer = new DerSignatureSerializer();
$serializedSig = $serializer->serialize($signature);
$sign = base64_encode($serializedSig);
var_dump($sign);
$data['sigdat'] = '__signature_sigdat__';
$json = json_encode($data);
$rt = $sm2->verifySign($json, $sign, unpack("H*", base64_decode('BNRhE10qHce4PRt8hCxAPfTmMDxW0Htw9SZHoUWn7U0Qj4GbU2Tgic4EmQSFjTcTdbDvNVmoSzwQvUkfzpRC9+k='))[1]);
dd($rt);
(1)云直联通知验签银行公钥与业务接口响应报文验签的银行公钥不是同一组公钥,请使用通知验签说明里面的银行公钥。 这个注意到了吧。
可能是 asn1 和 r+s 转换有问题吧。 用:github.com/lpilp/phpsm2sm3sm4 这个包试试,作者人很好,我当初对接招行时用的这个包。 也可以参考一下:www.02405.com/archives/12558
你直接联系招行的技术支持不行?
会不会是json_encode 转义的问题 包括斜杠 直接用 320