国密招商银行对接

前言#

国密主要有 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 位。

代码#

  • 安装包
    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 协议》,转载必须注明作者和本文链接
失色天空
本帖由系统于 2年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 40
DonnyLiu

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

2年前 评论

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

2年前 评论
失色天空 (楼主) 2年前
weiqingshan (作者) 2年前
weiqingshan (作者) 2年前
fanshi__ 2年前
时间不多咯 2年前
linpf 2年前
时间不多咯 2年前
pi_phq 1年前

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

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

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

2年前 评论

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

2年前 评论
╰ゝSakura

mark

2年前 评论

基于 openssl 开发的 sm2 国密扩展,已经和博主的代码比对过互通的,除了加密的结果多了个 04 开头的标记
gitee.com/state-secret-series/open...

2年前 评论
Eriksen

mark

2年前 评论

之前与该库的作者交流过,后面发现了招商银行 java 版本的一些签名方式的问题,因此后面作者特地针对招商银行的问题做了特殊的工作,具体的大家可以进该库的 github 地址看一下

2年前 评论

我调用这个 demo 签名加密可以,但是验签名一直返回 false。@

2年前 评论

楼主,你好,想问下,key 是怎么获取的啊?

1年前 评论

@linpf 你好, lajitengxun10086 这个是你的微信么,方便加你下么,有关于国密的技术问题

1年前 评论

加密的参数需要排序

1年前 评论
libra_1986 1年前
libra_1986 1年前
时间不多咯 (作者) 1年前
mmxhn1258 1年前

pfx 证书怎么转私钥

1年前 评论

file 上面的例子解密的时候稍作小小的修改就可以验证成功了!感谢大佬!

1年前 评论

有没有大佬处理过 SM4 密钥和向量是 32 位的?

1年前 评论

file 这个是 java 那边的 我用 sm2 加密和那边对不上 这个怎么搞

1年前 评论
chi-kwongli 1年前

解决不了的可以联系我 vx:onedayis86400s

1年前 评论
LOST

曾经碰了一鼻子灰,后来用银行提供的 SDK 部署了个专门处理加解密、签名、验签的服务,总算绕过去了

1年前 评论

laravel9 目前还不能使用这个包么?

1年前 评论