lpilp / phpsm2sm3sm4

php版本,支持国密SM2的签名算法,非对称加解密,SM3的hash, SM4的对称加解密
329 stars 77 forks source link

还是招行问题,看完了issue没有理解,求大佬解答,感谢 #48

Closed maturethan closed 1 year ago

maturethan commented 1 year ago

编辑下方便后来者们捡个现成 招行云直联,直接用大佬的sdk 加密完 \Rtgm\util\SmSignFormatRS::asn1_to_rs 过一下丢给招行 招行的返回, \Rtgm\util\SmSignFormatRS::rs_to_asn1 过一下再验签就行

重点: 验签公钥要用招行公钥,不是用户公钥 !!!! 验签公钥要用招行公钥,不是用户公钥 !!!! 验签公钥要用招行公钥,不是用户公钥 !!!!

1、关于加密, 大佬在其它issue和样例代码提到需要先format_cmbc,但我实测不行,用别人的代码加密可以过

   //标准处理sm2 sign
    $key = base64_decode($this->_priKey);
    $key = bin2hex($key);//转为16进制
    $sm2 = new RtSm2("base64");
    $sm2_sign = $sm2->doSign($data_str, $key, $iv);
    $sm2_sign = trim($sm2_sign);
    //招行单独处理-大佬您的
    $fs = new \Rtgm\util\FormatSign();
    $final_sign = $fs->format_cmbc($sm2_sign);
    //招行单独处理-别人
    $tmp = base64_decode($sm2_sign);
    $a = \FG\ASN1\ASNObject::fromBinary($tmp)->getChildren();
    $aa = $this->_formatHex($a[0]->getContent());
    $bb = $this->_formatHex($a[1]->getContent());
    $cc = $aa . $bb;
    $final_sign = base64_encode(hex2bin($cc));
    //最终别人的可以过招行,但是大佬的不行       

想了解是为什么呢,别人的代码我是learnku看的(地址/articles/68557)

2、关于解密 我收到招行response后

    $bak_sign = $array["signature"]["sigdat"];
    $fs = new \Rtgm\util\FormatSign();
    $bak_sign2 = $fs->run($bak_sign);

会卡死,无法解密

但是如果不用大佬的代码,直接

    $array["signature"]["sigdat"] = '__signature_sigdat__';
    $array = $this->_dataSort($array);

    $sm2 = new RtSm2("base64");
    $json = json_encode($array, JSON_UNESCAPED_UNICODE);
    $signHex = bin2hex(base64_decode($bak_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 = bin2hex(base64_decode($this->_pubKey));
    $b = $sm2->verifySign($json, $sign, $publicKey, $userId);

必定校验不过 运行大佬的demo是没问题的,没有思路,求大佬赐教,感谢感谢

lpilp commented 1 year ago

你先试下,不经过format直接使用,这个我以前试了下大概是几百分之一会出问题,经过处理是为了处理那个几百分1的问题,如果你试了好多组都有问题的话, 那可能现在招行改了代码的返回形式了, 还不行的话,你给一个签名,我试试,只给签名就行,

lpilp commented 1 year ago
   //招行单独处理-别人
    $tmp = base64_decode($sm2_sign);
    $a = \FG\ASN1\ASNObject::fromBinary($tmp)->getChildren();
    $aa = $this->_formatHex($a[0]->getContent());
    $bb = $this->_formatHex($a[1]->getContent());
    $cc = $aa . $bb;
    $final_sign = base64_encode(hex2bin($cc));

如果是这样可以的话就是招行改了签名的代码,正常的代码asn1(r,s ) , 这个 返回的 r + s ,如果别人的这种可以的话,你也这么处理,不过他这个处理也会有那个字节少的问题,aa, bb都应该是32字节的16进制,就是64的长度,如果长度不够前面得补0

lpilp commented 1 year ago

你再看看看招行的文档,可能招行给的是两种形式,根据不同的参数返回不一样的签名,一种的asn1(r,s) ,就是我的代码里的, 还有一种的纯的 r +s 格式的

maturethan commented 1 year ago

你先试下,不经过format直接使用,这个我以前试了下大概是几百分之一会出问题,经过处理是为了处理那个几百分1的问题,如果你试了好多组都有问题的话, 那可能现在招行改了代码的返回形式了, 还不行的话,你给一个签名,我试试,只给签名就行,

FAKcV6OrxEFS+vZjWW9SdqxZl0AR3FMg0tsxVEjLNhL3wkXafXnInG2Y+bBpQE22vpusJNY8uL2Pu2YcdF3VBg==

感谢大佬的回复,这是请求招行后招行返回的签名,大佬帮忙掌掌眼

lpilp commented 1 year ago

就是我说的这是 r+s 格式的, 区别见 readme里常见问题,文档最后一行,解决方案是做一下 转换, 在src/util/SmSignFormatRS.php , 签名后将asn1转 r+s的格式,就是上面的那个 “//招行单独处理-别人” , 验证的时候,将别人的r+s 转成 asn1, 或者你你再看看招行给的文档,是否有相关的参数可以要求对方的返回值是 asn1方式,以前都是asn1方式的,应该改版后也要兼容以前的人的接入吧

maturethan commented 1 year ago

就是我说的这是 r+s 格式的, 区别见 readme里常见问题,文档最后一行,解决方案是做一下 转换, 在src/util/SmSignFormatRS.php , 签名后将asn1转 r+s的格式,就是上面的那个 “//招行单独处理-别人” , 验证的时候,将别人的r+s 转成 asn1, 或者你你再看看招行给的文档,是否有相关的参数可以要求对方的返回值是 asn1方式,以前都是asn1方式的,应该改版后也要兼容以前的人的接入吧

大佬真的很抱歉,我太菜了 目前加签请求招行没问题了

    //排序生json
    $resData = $this->_dataSort($resData);
    $data_str = json_encode($resData, JSON_UNESCAPED_UNICODE);
    //sm2 sign
    $priKey = bin2hex(base64_decode($this->_priKey));
    $sm2 = new RtSm2("base64");
    $sm2_sign = $sm2->doSign($data_str, $priKey, $iv);
    //招行单独处理-github lpilp大佬
    $final_sign = \Rtgm\util\SmSignFormatRS::asn1_to_rs($sm2_sign);
    //上签
    $httpDataStr = str_replace("__signature_sigdat__", $final_sign, $data_str);

但是收到招行的返回,验签还是false

    $bak_sign = $array["signature"]["sigdat"];
    $array["signature"]["sigdat"] = '__signature_sigdat__';
    $bak_sign = \Rtgm\util\SmSignFormatRS::rs_to_asn1($bak_sign);
    $array = $this->_dataSort($array);
    $json = json_encode($array, JSON_UNESCAPED_UNICODE);
    $publicKey = bin2hex(base64_decode($this->_pubKey));
    $sm2 = new RtSm2("base64");
    $b = $sm2->verifySign($json, $bak_sign, $publicKey, $userId);

大佬能不能再帮看下我验签是不是哪里还有不对

lpilp commented 1 year ago

可能会有以下3个问题: 1 确认返回的签名是 asn1,还是r+s的,(10%) 2 $json 的值的问题(你算出来是否和他加签的时候一样),这个可以看给的sdk 或是文档 (30%) 3 pubKey的问题, 我这函数是支持的是 128(130的话是前面有04) hex 的公钥,如果一开始的公钥比较长的话,可能是asn1过的公钥,得解出公钥,使用 test/tasn1.php 可以解开下看下公钥,替换 (60%可能这个问题)

maturethan commented 1 year ago

大佬我补充下,我用我自己request招行的字串直接验签,是能true的 但是招行 reponse 我的字串就验不过

maturethan commented 1 year ago

可能会有以下3个问题: 1 确认返回的签名是 asn1,还是r+s的,(10%) 2 $json 的值的问题(你算出来是否和他加签的时候一样),这个可以看给的sdk 或是文档 (30%) 3 pubKey的问题, 我这函数是支持的是 128(130的话是前面有04) hex 的公钥,如果一开始的公钥比较长的话,可能是asn1过的公钥,得解出公钥,使用 test/tasn1.php 可以解开下看下公钥,替换 (60%可能这个问题)

好的真的非常感谢大佬,我研究看看

lpilp commented 1 year ago

大佬我补充下,我用我自己request招行的字串直接验签,是能true的 但是招行 reponse 我的字串就验不过

那就是第2条的问题比较大,你看下他们的文档,可能那个signature这个字段不参与签名

maturethan commented 1 year ago

可以了,感恩