php 与java对接国密sm2遇到的一些问题(招商银行接口)
最近在对接招行的云直联接口,使用国密算法,加签加密逻辑为:使用用户SM私钥签名->用户的对称(SM4)密钥加密;由于招行只提供了C#和java的对接示例所以需要找一个可用的PHP国密的包,在github上找到几个但是都无法顺利验签成功,特地来问下各位有什么办法(SM4加解密已验证通过,就是SM2签名一致提示无效)。
先说下第一个包:
github.com/lpilp/phpsm2sm3sm4
招行提供的用户私钥(SM2):
NBtl7WnuUtA2v5FaebEkU0/Jj1IodLGT6lQqwkzmd2E=
我方调用代码:
use Rtgm\sm\RtSm2;
$key='NBtl7WnuUtA2v5FaebEkU0/Jj1IodLGT6lQqwkzmd2E=';
$key=bin2hex($key);//转为16进制
$sm2 = new RtSm2('base64');
$sign = $sm2->doSign($data, $key);
$sign=trim($sign);
这里遇到第一个问题是,$sm2->doSign($data, $key)方法只支持16进制的密码
如果不将私钥转为16进制的话调用就会报错:
附上_dosign()截图
如果将私钥转为16进制的话调用的话签名正常,但是java验签不通过。
第二个包:
github.com/lat751608899/sm2
调用示例
$key='NBtl7WnuUtA2v5FaebEkU0/Jj1IodLGT6lQqwkzmd2E=';
$key=base64_decode($key);
$pubKey = new \Lat\Ecc\PublicKey();
$pubKey->parse($key);
$sm2 = new \Lat\Ecc\Sm2();
$res = $sm2->pubEncrypt($pubKey, $data);
dd($res);
出现以下异常
不知各位有什么建议或者有没有和招行那边对接国密的PHP demo?
附上招行给的java示例:gitee.com/zhong-xiaokai/cmbjavasm
==============================================================================
最终在latzou(github.com/lat751608899) 的帮助下完成了对接,非常感谢!
具体demo如下,后面有对接需要的小伙伴可以参考下:
<?php
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;
require 'vendor/autoload.php';
//sm2签名
$data = '{"request":{"body":{"TEST":"中文","TEST2":"!@#$%^&*()","TEST3":12345,"TEST4":[{"arrItem1":"qaz","arrItem2":123,"arrItem3":true,"arrItem4":"中文"}],"buscod":"N02030"},"head":{"funcode":"DCLISMOD","userid":"N003261207"}},"signature":{"sigdat":"__signature_sigdat__"}}';
$key = 'NBtl7WnuUtA2v5FaebEkU0/Jj1IodLGT6lQqwkzmd2E=';
$key = bin2hex(base64_decode($key));//转为16进制
$sm2 = new RtSm2('base64');
$userid = 'N003261207' . "0000000000000000";
$userid = substr($userid, 0, 16);
$sign = $sm2->doSign($data, $key, $userid);
$sign = base64_decode($sign);
$a = \FG\ASN1\ASNObject::fromBinary($sign)->getChildren();
$aa = formatHex($a[0]->getContent());
$bb = formatHex($a[1]->getContent());
$sign = $aa. $bb;
$sign = base64_encode(hex2bin($sign));
var_dump($sign);
//sm4加密
$iv = substr($userid.'0000000000000000', 0,16) ;
$sm4 = new RtSm4($key);
$sign = $sm4->encrypt($data,'sm4',$iv,'base64');
//sm2验签
$signHex = bin2hex(base64_decode($sign));
var_dump($signHex);
$r = substr($signHex, 0, 64);
$s = substr($signHex, 64, 64);
var_dump($r, $s);
$r = gmp_init($r, 16);
$s = gmp_init($s, 16);
/*$r = gmp_init('90416529259334433398865842692135340273188180784859666141339740103133164395295', 10);
$s = gmp_init('51927610271972364114244381230895889971736075490328811928131691394657016568041', 10);*/
$signature = new Signature( $r, $s );
$serializer = new DerSignatureSerializer();
$serializedSig = $serializer->serialize($signature);
$sign = base64_encode($serializedSig);
var_dump($sign);
$adapter = RtEccFactory::getAdapter();
$generator = RtEccFactory::getSmCurves()->generatorSm2();
$secret = gmp_init($key, 16);
$key = new PrivateKey($adapter, $generator, $secret);
$pubkey = $key->getPublicKey()->getPoint();
$x = $pubkey->getX();
$y = $pubkey->getY();
$pub = gmp_strval($x, 16);
$pub .= gmp_strval($y, 16);
var_dump($pub);
$b = $sm2->verifySign($data, $sign, $pub, $userid);
var_dump($b);
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;
}
问题已解决,非常感谢latzou,具体方案已附到原文