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进制的密码

php 与java 对接国密sm2遇到的一些问题
如果不将私钥转为16进制的话调用就会报错:

php 与java 对接国密sm2遇到的一些问题
附上_dosign()截图

php 与java 对接国密sm2遇到的一些问题
如果将私钥转为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 与java 对接国密sm2遇到的一些问题

不知各位有什么建议或者有没有和招行那边对接国密的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,具体方案已附到原文

1年前 评论
hon-陈烁临 1年前
linpf 1年前
stupidlove 10个月前
讨论数量: 42
use Rtgm\sm\RtSm2;

$key=base64_decode('NBtl7WnuUtA2v5FaebEkU0/Jj1IodLGT6lQqwkzmd2E=');
$key=bin2hex($key);//转为16进制
$sm2 = new RtSm2('base64');
$sign = $sm2->doSign($data, $key);
$sign=trim($sign);

这样试下吧

1年前 评论
昵称过于个性导致系统无法显示 (楼主) 1年前
昵称过于个性导致系统无法显示 (楼主) 1年前
deatil (作者) 1年前
deatil (作者) 1年前
use Rtgm\sm\RtSm2;

$key=base64_decode('NBtl7WnuUtA2v5FaebEkU0/Jj1IodLGT6lQqwkzmd2E=');

$key=bin2hex($key);//转为16进制
$sm2 = new RtSm2('base64');
$sign = $sm2->doSign($data, $key);
$sign=trim($sign);

改了的这个就该是对的

1年前 评论
昵称过于个性导致系统无法显示 (楼主) 1年前
deatil (作者) 1年前
deatil (作者) 1年前
昵称过于个性导致系统无法显示 (楼主) 1年前
deatil (作者) 1年前
昵称过于个性导致系统无法显示 (楼主) 1年前
deatil (作者) 1年前

file
这个包应该没问题,只不过里面的代码逻辑可能出了点问题.方便截个_dosign函数的图吗?年前我对接银行支付的时候好像遇到过这种问题,怎样处理的记不太清了

1年前 评论
昵称过于个性导致系统无法显示 (楼主) 1年前

前段时间处理过招行国密的问题,用go实现的,然后供php调用

1年前 评论

半年前处理过一个邮储的国密问题,直接用java的demo做成java的webserver供php调用,你用的这个php包我也试过了,签名出来与java结果不一致,我还试了其它能找到的php包,都不行

1年前 评论
renxiaotu (作者) 1年前

file
找到调用的这个方法,
这句
$dec = gmp_init($dec, 10);
改成
if (is_string($dec)){
$dec = gmp_init($dec, 10);
}

原来测试demo示例

PHP
加密的数据不需要转化16进制, json_encode一下就可以,咱们报错是一样的

1年前 评论

问题已解决,非常感谢latzou,具体方案已附到原文

1年前 评论
hon-陈烁临 1年前
linpf 1年前
stupidlove 10个月前

您好,我也遇到了招行这个国密的问题,按照您的demo还是失败,具体截图如下:

file

file

还有这个提示

PHP

1年前 评论
昵称过于个性导致系统无法显示 (楼主) 1年前
昵称过于个性导致系统无法显示 (楼主) 1年前
wotainanle (作者) 1年前
昵称过于个性导致系统无法显示 (楼主) 1年前
wotainanle (作者) 1年前

周末这么快回复,真的感谢!!!
用了demo中的数据,也对KEY做了ASSIIC码排序,请求结果还是不行

PHP

1年前 评论
A_sneeze_is_missing 1年前

楼主你好,我在跟你之前对接同样的业务 我按照你的方法 调用 但是出了点小问题 不知道怎么弄 求解答一下谢谢了

file

1年前 评论

@昵称过于个性导致系统无法显示 楼主你好, 请求完后的数据 验签使用银行给的公钥么, 我看代码中没有使用公钥 $pub = gmp_strval($x, 16); $pub .= gmp_strval($y, 16); var_dump($pub); $b = $sm2->verifySign($data, $sign, $pub, $userid);

1年前 评论
hashxb 1年前
fanshi__ (作者) 1年前

我之前用 nodejs 对接招行的,搞了几天还是放弃了,换前置机接入,省事多了 :joy:

1年前 评论

楼主您好,我也遇到同样的问题,此前在测试环境应该是提供的签名不验证,转入生产环境后,一直是“ErrMsg:DCKY016-解密失败,如您之前交易正常,请确认密钥是否有变更”,重新生成过密钥也提交并且审核了,找不到什么原因了,已按照您上面给出的方法修改了,能否留个联络方式QQ:16535195,哪位大神已经解决了,我们互相学习学习~

1年前 评论

楼主你好,按照你的demo写的,银行端一直提示是签名不对,是签名程序不对吗?

错误提示:ErrMsg:DCKY005-无效签名:Failed to verify data using SM2 public key. case by : It’s just a failure to check, not an exception.
有哪位大神指点一下!谢谢!!!

主要问题已经解决了,只是验签有点小问题,不影响使用,感谢楼主分享!

1年前 评论
ming 10个月前

这两天在对接泸州银行支付,也是用的国密,sm3签名,sm2解密,sm4加密,搞得很乱,给的demo 只有java 和C#,解密过程密钥也处理过了,C1部分也加入了随机数,最坑的是 kdf函数生成过程又是用SHA256算法,我魔改了几天,用PHP翻译一遍JAVA的功能,现在改不动了,很难受,做了个JAVA的服务节点做签名验证,老板又不同意,说技术栈太多了,不好维护,那个大兄弟过sm2 C1C2C3模式的解密,中间C1模块他们混淆过,key 也加工过附上代码:

public String decrypt(byte[] encryptData, BigInteger privateKey) {
        byte[] C1Byte = new byte[65];
        System.arraycopy(encryptData, 0, C1Byte, 0, C1Byte.length);

        ECPoint C1 = curve.decodePoint(C1Byte).normalize();

        /* 计算[dB]C1 = (x2, y2) */
        ECPoint dBC1 = C1.multiply(privateKey).normalize();

        /* 计算t = KDF(x2 || y2, klen) */
        byte[] dBC1Bytes = dBC1.getEncoded(false);
        DerivationFunction kdf = new KDF1BytesGenerator(new ShortenedDigest(new SHA256Digest(), 20));

        int klen = encryptData.length - 65 - 20;

        byte[] t = new byte[klen];
        kdf.init(new ISO18033KDFParameters(dBC1Bytes));
        kdf.generateBytes(t, 0, t.length);

        if (allZero(t)) {
            throw new RuntimeException("all zero");
        }

        /* 5 计算M'=C2^t */
        byte[] M = new byte[klen];
        for (int i = 0; i < M.length; i++) {
            M[i] = (byte) (encryptData[C1Byte.length + i] ^ t[i]);
        }

        /* 6 计算 u = Hash(x2 || M' || y2) 判断 u == C3是否成立 */
        byte[] C3 = new byte[20];
        System.arraycopy(encryptData, encryptData.length - 20, C3, 0, 20);
        byte[] u = calculateHash(dBC1.getXCoord().toBigInteger(), M, dBC1.getYCoord().toBigInteger());
        if (Arrays.equals(u, C3)) {
            return new String(M);
        } else {
            throw new RuntimeException("解密验证失败");
        }
    }

    private byte[] calculateHash(BigInteger x2, byte[] M, BigInteger y2) {
        ShortenedDigest digest = new ShortenedDigest(new SHA256Digest(), 20);
        byte[] buf = x2.toByteArray();
        digest.update(buf, 0, buf.length);
        digest.update(M, 0, M.length);
        buf = y2.toByteArray();
        digest.update(buf, 0, buf.length);

        buf = new byte[20];
        digest.doFinal(buf, 0);
        return buf;
    }

php使用的是gitcode.net/mirrors/lpilp/phpsm2sm... 上面的包,将密钥,密文传入,解密不了 ,有根据java 的逻辑修改了部分,结果解出来乱码,老板说用PHP把java翻译一遍,遇到的问题太多了,总感觉方向不对,现在很头疼

8个月前 评论
justmd5 8个月前

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