招商银行验签问题
1. 运行环境
windows10 .docker中
1). 当前使用的 Laravel 版本?
laravel10
//: <> (使用 php artisan --version
命令查看)
2). 当前使用的 php/php-fpm 版本?
PHP 版本: php8.2
php-fpm 版本:
3). 当前系统
Windows 10
//: <> (期待数值 Windows 10 / Ubuntu 20.4 / CentOS 8 )
4). 业务环境
开发环境
//: <> (期待信息 开发环境
或 生产环境
)
//: <> (是否使用负载均衡?请提供相关信息)
5). 相关软件版本
Nginx 、MySQL
//: <> (提供相关软件的版本,如 Nginx 、MySQL、MongoDB 等)
2. 问题描述?
对接的招商银行的银企直连接口。
1、使用了 lpilp/guomi 的包
2、签名用的给的示例代码
签名是可以的,但是验签的时候始终为 false
//: <> (代码问题的话,请提供一份最短的,可复现问题的代码。或者相关代码)
<?php
#招商银行银企直联国密免前置/SaaS对接示例,本示例仅供参考,不保证各种异常场景运行,请勿直接使用,如有错漏请联系对接人员。运行时,请使用所获取的测试资源替换 用户编号、公私钥、对称密钥、服务商编号等信息。
namespace cmb;
use Mdanter\Ecc\Crypto\Signature\Signature;
use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;
use \FG\ASN1\ASNObject;
use Rtgm\sm\RtSm2;
use Rtgm\sm\RtSm4;
class DcHelper
{
// 请求URL
var $url;
// 企业网银用户号
var $uid;
// 国密算法向量,根据用户号生成
var $userId;
// 算法,固定为国密算法
var $alg;
// 客户私钥
var $privateKey;
// 银行公钥
var $publicKey;
// 协商的对称密钥
var $symKey;
function __construct($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) {
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);
}
}
}
}
下面是调用结果
<?php
#招商银行银企直联国密免前置/SaaS对接示例,本示例仅供参考,不保证各种异常场景运行,请勿直接使用,如有错漏请联系对接人员。运行时,请使用所获取的测试资源替换 用户编号、公私钥、对称密钥、服务商编号等信息。
require 'vendor/autoload.php';
require 'dchelper.php';
use cmb\DcHelper;
// 测试地址,生产需要替换
$url = "http://cdctest.cmburl.cn:80/cdcserver/api/v2";
// 生产地址
// $url = "https://cdc.cmbchina.com/cdcserver/api/v2";
// 银行公钥,生产需要替换
$publicKey = 'BNsIe9U0x8IeSe4h/dxUzVEz9pie0hDSfMRINRXc7s1UIXfkExnYECF4QqJ2SnHxLv3z/99gsfDQrQ6dzN5lZj0=';
// 生产环境银行公钥
// $publicKey = 'BEynMEZOjNpwZIiD9jXtZSGr3Ecpwn7r+m+wtafXHb6VIZTnugfuxhcKASq3hX+KX9JlHODDl9/RDKQv4XLOFak=';
// 客户私钥,生产需要替换
$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 . '"}}';
// 创建帮助类,并传入构造参数
$dchelper = new DcHelper($url, $uid, $privateKey, $publicKey, $symKey);
// 发送请求内容,并接收返回数据
$response = $dchelper->sendRequest($data, $funcode);
echo '响应报文:<br/>' . $response;
3. 您期望得到的结果?
给的示例可以跑。签名也可以。但是他的回调数据验签就是过不去
回调数据 。
这是返回数据 就很奇怪
{
"notdat": "{\"msgdat\":{\"trsInfo\":{\"nusAge\":\"打款\",\"rtnFlg\":\"S\",\"reqNbr\":\"8782029704\",\"crtBnk\":\"招商银行\",\"dbtAcc\":\"128964320010000\",\"eptTim\":\"0\",\"eptDat\":\"20241024\",\"crtNam\":\"殷萌涵\",\"busNar\":\"\",\"crtAdr\":\"招商银行\",\"dbtBbk\":\"75\",\"ntfCh2\":\"\",\"ntfCh1\":\"\",\"stlChn\":\"Q\",\"crtBbk\":\"\",\"oprDat\":\"20241024\",\"trxSeq\":\"C0146ZR0001QF2Z\",\"crtAcc\":\"6214837811201866\",\"bnkFlg\":\"Y\",\"reqSts\":\"FIN\",\"trxSet\":\"BHP6ZR0800558CT\",\"dbtNam\":\"银企直连测试用户专用06\",\"yurRef\":\"202410241717460000003222545\",\"ccyNbr\":\"10\",\"trsAmt\":\"5.55\"}},\"msgtyp\":\"FINS\"}",
"notkey": "128964320010000",
"notnbr": "258409058866954240",
"nottyp": "YQN02030",
"sigdat": "GtNpYJTX9bnpFbO8eFzBCwQGrj+rPmOq7gb0wCIlxbWMGImlrhO8gIP8z4UmlBKnn1cvtmIuTtoDqs8+QMz28g==",
"sigtim": "20241024171815",
"usrnbr": "U006566025"
}
4. 您实际得到的结果?
验签过不去
//: <> (有报错信息的话把堆栈信息提供出来)
推荐文章: