招商银行回调通知验签过不了。麻烦大佬们看下

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);
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 5

(1)云直联通知验签银行公钥与业务接口响应报文验签的银行公钥不是同一组公钥,请使用通知验签说明里面的银行公钥。 这个注意到了吧。

3个月前 评论
aq009 (楼主) 3个月前

可能是 asn1 和 r+s 转换有问题吧。 用:github.com/lpilp/phpsm2sm3sm4 这个包试试,作者人很好,我当初对接招行时用的这个包。 也可以参考一下:www.02405.com/archives/12558

3个月前 评论

你直接联系招行的技术支持不行?

3个月前 评论

会不会是json_encode 转义的问题 包括斜杠 直接用 320

3个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!