国密招商银行对接

前言

国密主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。

  • SM1
    对称加密。其加密强度与AES(高级加密标准,Advanced Encryption Standard)相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。

  • SM2
    非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。

  • SM3
    消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。

  • SM4
    对称加密,无线局域网标准的分组数据算法,密钥长度和分组长度均为128位。

代码

招商银行都知道吧,我们对接它,它给的java版本的示例,看球不懂,我丢。我反手直接给个php的,复制到你的项目中改改参数就行

  • 安装包
    composer require lpilp/guomi

  • 示例

    // 工具函数
    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;
    }
    ############################数据加密开始################################
    // 公钥
    $publicKey = 'BNsIe9U0x8IeSe4h/dxUzVEz9pie0hDSfMRINRXc7s1UIXfkExnYECF4QqJ2SnHxLv3z/99gsfDQrQ6dzN5lZj0=';
    // 私钥
    $privateKey = 'NBtl7WnuUtA2v5FaebEkU0/Jj1IodLGT6lQqwkzmd2E=';
    // base64私钥转二进制
    $privateKey = base64_decode($privateKey);
    // 二进制转十六进制字符串
    $privateKey = unpack("H*", $privateKey)[1];
    // 待加密的数据
    $data       = '{"request":{"body":{"ntbusmody":[{"busmod":"00001"}],"ntdumaddx1":[{"bbknbr":"75","dyanam":"招商测试","dyanbr":"11111111111","eftdat":"20220602","inbacc":"755936020410404","ovrctl":"N","yurref":"596620626253316098"}]},"head":{"funcode":"NTDUMADD","reqid":"202206021511010000001","userid":"B000001631"}},"signature":{"sigdat":"__signature_sigdat__","sigtim":"20220602161503"}}';
    // 生成签名开始
    $sm2    = new RtSm2("base64");
    // 将用户id填充到16个字节
    $userId = sprintf('%-016s', "B000001631");
    // 使用rsa的私钥生成签名(注意这里是私钥!私钥!私钥!)
    $sign   = $sm2->doSign($data, $privateKey, $userId);
    // 将base64的签名还原为二进制
    $sign   = base64_decode($sign);
    // 处理二进制数据
    $point  = \FG\ASN1\ASNObject::fromBinary($sign)->getChildren();
    $pointX = formatHex($point[0]->getContent());
    $pointY = formatHex($point[1]->getContent());
    $sign   = $pointX . $pointY;
    $sign   = base64_encode(hex2bin($sign));
    // 替换签名字段
    $data = str_replace('__signature_sigdat__', $sign, $data);
    // 对数据进行对称加密(换成你自己的key)
    $sm4         = new RtSm4('VuAzSWQhsoNqzn0K');
    // 这里使用的具名参数的写法,低版本的php改成顺序传入参数就行
    $encryptData = $sm4->encrypt($data, 'sm4-cbc', $iv = $userId, "base64");
    var_dump($encryptData);die;
    ############################数据加密结束################################
    ############################返回数据验证开始################################
    $decryptData = "LkQOOa0kJr7xWxyhr1kj4mf31f1lZOv5bURemjcALkmQXGeKBIVnR6f+BIN8g6UvhHy08LKrmyYTq9LBXQBI95i7Ht/4OWTRFoFG/lCYT39cr50a426UgreuF4NUrUdCGoItHiwTmCcfJStqjdGXY0O0lr9YR2GJZEOtpllnRThoIWEIdPUvQMtUyzfQKuOZ6s7r6V3jirKUFuaeuFtuZ96RliOCqQa/BdCY/qHnjVaMEoZNTYeHeUIcZs43nCxaMcvaBFTZ9wbBjNf3jwmi/TZKHIcXLQpIxtWdYoOC12dgKkeBL83xaHCGYpvkOO0IFML8XbJR1oQJdvvF49WCN6HmrcikG0fPjX+AzTxT1odHsAwHk78m9galKfkslUDrT+bq4qplw3ByOQA+5WfzmNPsSgGYLfE6va+5EbXieaMW6pPs7yiWUyOhpVOpBV+6q4cwXWeGgDgUhXQ1dTKFqqJQBMKX8iRvXgYFTmwSzZHvH7VZmtuf7gZMMtycSUFb";
    // 返回结果解密,这里使用的具名参数的写法,低版本的php改成顺序传入参数就行
    $json = $sm4->decrypt($decryptData, $type = 'sm4-cbc', $iv = $userId, $formatInput = 'base64');
    $data = json_decode($json, true);
    var_dump($data);die;
    // 验证签名是否正确
    $sign = $data["signature"]["sigdat"];
    // 将数据中的签名重置
    $data["signature"]["sigdat"] = "__signature_sigdat__";
    $json                        = json_encode($data, 256);
    $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 \Mdanter\Ecc\Crypto\Signature\Signature($r, $s);
    $serializer                  = new DerSignatureSerializer();
    $serializedSig               = $serializer->serialize($signature);
    $sign                        = base64_encode($serializedSig);
    $publicKey                   = unpack("H*", base64_decode($publicKey))[1];
    $b                           = $sm2->verifySign($json, $sign, $publicKey, $userId);
    var_dump($b);
    ############################返回数据验证结束################################
本作品采用《CC 协议》,转载必须注明作者和本文链接
失色天空
本帖由系统于 1周前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 11

不错,最近刚好有接触这块的业务

3周前 评论

亲您好,我运行了你的demo,发现最后一步校验签名的结果为flase,请问这需要改动吗?

2周前 评论
失色天空 (楼主) 2周前
weiqingshan (作者) 2周前
weiqingshan (作者) 2周前
fanshi__ 5天前

ubuntu安装的php和openssl默认支持 国密算法。

php -r "var_dump(openssl_get_md_methods());"
// 输出: [19] => string(3) "sm3"
1周前 评论

最近刚对接了江苏银行e融支付,也只有java版本。
接口和文档字段有出入,java demo跑不通,测试支付环境整的也跑不通
唉,直接在生产环境,对接了。其中费了点事就是证书加密后和银行那对不上。

1周前 评论

@失色天空 调用招行接口有的验签返回true 有的返回false,添加账户子单元返回验签不成功

5天前 评论

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