lpilp / phpsm2sm3sm4

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

对接招行付款码收款支付,需要对终端数据进行加密,并且在要对随机私钥进行数字信封加密,进行测试时信封加密和招行不一致,加密结果也不一致 #66

Closed zhiYu2017 closed 10 months ago

zhiYu2017 commented 10 months ago

测试示例代码如下 public function testSM4Key(){ $selfPrivateKey = "1234567890123456";//自有测试密钥 $sm4CbcIv = "0000000000000000";//向量值 $publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE6Q+fktsnY9OFP+LpSR5Udbxf5zHCFO0PmOKlFNTxDIGl8jsPbbB/9ET23NV+acSz4FEkzD74sW2iiNVHRLiKHg==";//招行测试公钥 $tStart = "3059301306072a8648ce3d020106082a811ccf5501822d03420004";//招行标准公钥头

    $param = [
        "device_type" => "11",
        "device_id" => "HZT000E0"
    ];
    $paramString  = json_encode($param, JSON_UNESCAPED_SLASHES);
    $sm4 = new RtSm4($selfPrivateKey);
    $sign = $sm4->encrypt($paramString,"sm4",$sm4CbcIv,'base64');
    //招行提供他们加密的加密值
    $cmbKey = "Jo80yoJVBjVYv5OCFg6tVG1WiGZYXIWuhwbgIqdFBNo7ci8I53vasPSh59bu7HIg";

    $sm2 = new RtSm2('base64');
    $bStr=base64_decode($publicKey);//base64解码公钥
    $pk=bin2hex($bStr);//转16进制
    //去公钥头
    $pkArr=explode($tStart,$pk);
    $pubKey=$pkArr[1];//实际公钥
    $mySign = $sm2->doEncrypt($selfPrivateKey, $pubKey);
    //招行提供他们加密的信封值
    $cmbSign = "MHgCIAfeauGU/Iw1JMRsKoEy2F788jWKqtVjeDmgzLL+jDWpAiApIlLZFh1cr3NsegaF8AAO85WYJOR1gxT63rQwrZAH7QQgjWAOGPRLUAPy8VdtkGX6ApZM5q4NqWNMk2oNd2ZcHQYEEF3QWeShoEp/MOit8HYv5ME=";

}

这是招行提供的java版本的代码示例

//**

目前是SM4加密的值和SM2数字信封加密都和招行跑的不一致,麻烦大佬帮忙看下具体问题在哪里。谢谢。

lpilp commented 10 months ago

你这方法弄错啦,不是 sm2签名, 正常的流程是:对接的参数使用 sm4加密, 加密的密码使用sm2公钥非对称加密,你弄成签名了,还有一个是我这缺省使用了固定中间椭圆,被招行加黑名单了, $sm2 = new RtSm2('base64',false); 构造这里第二参数设为 false , 本地测试可以设为true, 这样每次加密的值都长一样比较好调试,设为false后,同样加密串每次都不一样,不好调

zhiYu2017 commented 10 months ago

按照大佬的提示修改完代码,自我加解密都是成功的,但是传给招行就是解密失败,暂时没找到原因

lpilp commented 10 months ago

按照大佬的提示修改完代码,自我加解密都是成功的,但是传给招行就是解密失败,暂时没找到原因

问题1: 格式上不对呢,JAVA sm2的非对称加密中

byte[] wEncKeyByt = cipherRawToAsn1(sm2EncryptOld(wSm4Key.getBytes(), bcecPublicKey));

这个java里做了一个asn1, 为asn1(c1x,c1y, c3,c2) , 我的生成的是 c1c3c2 , 就是你在上一个已关的issue里 里的问题,所以要进行转换

2 那个 sm4 的向量,

     byte[] wEncByt = GmUtil.sm4EncryptCBC(wSm4Key.getBytes(), contents[i].getBytes(), SM4_CBC_IV);

用到的向量SM4_CBC_IV, 这个得修改成与你的一致,

zhiYu2017 commented 10 months ago

SM4_CBC_IV 招行的值是 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} ,我处理成字符串是16个0:"0000000000000000",这个是否正确

lpilp commented 10 months ago

SM4_CBC_IV 招行的值是 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} ,我处理成字符串是16个0:"0000000000000000",这个是否正确

不是呢, 字符‘0’ 是\0x30 , 你可以用 $iv = str_repeat(chr(0),16)

zhiYu2017 commented 10 months ago

sm4向量问题已经解决,格式转化的问题我看了demo是将asn1(c1x,c1y,c3,c2)转成c1c3c2,如何反转呢

lpilp commented 10 months ago

https://github.com/lpilp/simplesm2/blob/main/src/smecc/SPLSM2/Sm2Asn1.php

function asn1_cccc($c1x, $c1y, $c3, $c2, $outFormat = 'hex')

zhiYu2017 commented 10 months ago

感谢大佬,是格式的问题。只需要将sm2加密后的结果转成base64。不用将c1c3c2转成asn1(c1x,c1y,c3,c2)。 大佬上个赞助码吧,请大佬喝个咖啡。 示例代码如下

 $selfPrivateKey = "1234567890123456";//自有测试密钥
        $sm4CbcIv = str_repeat(chr(0x00),16);
        //$privateKey = "D5F2AFA24E6BA9071B54A8C9AD735F9A1DE9C4657FA386C09B592694BC118B38";
        $publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE6Q+fktsnY9OFP+LpSR5Udbxf5zHCFO0PmOKlFNTxDIGl8jsPbbB/9ET23NV+acSz4FEkzD74sW2iiNVHRLiKHg==";//招行测试公钥
        $tStart = "3059301306072a8648ce3d020106082a811ccf5501822d03420004";//招行标准公钥头

        $param = [
            "device_type" => "###",
            "device_id" => "####"
        ];
        $paramString  = json_encode($param, JSON_UNESCAPED_SLASHES);
        $sm4 = new RtSm4($selfPrivateKey);
        //这是SM4加密结果
        $sign = $sm4->encrypt($paramString,"sm4",$sm4CbcIv,'base64');

        $sm2 = new RtSm2('base64');
        $bStr=base64_decode($publicKey);//base64解码公钥
        $pk=bin2hex($bStr);//转16进制

        //去公钥头
        $pkArr=explode($tStart,$pk);
        $pubKey=$pkArr[1];//实际公钥
        $mySign = $sm2->doEncrypt($selfPrivateKey, $pubKey);
        //需要将他转化为base64,这是数字信封结果
        $mySign = base64_encode(hex2bin($mySign));
lpilp commented 10 months ago

成功了就好!