lpilp / phpsm2sm3sm4

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

关于招行某些接口的数字信封加密 sm2解密失败问题 #46

Closed sjp325 closed 1 year ago

sjp325 commented 1 year ago

你好大佬,最近有个问题卡了好久,招行的一些接口的返回数据进行了数字信封加密,就是自定义key通过sm2 加密,,我们拿到加密的key ,解出key ,再key用sm4解出对应的加密数据,现在问题是解不出这个key。不懂哪里出问题,能帮我看一下不 我这边的代码

$privateKey ='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$encryptKey = 'yyyyyyyyyyyyyyyyyyyyyyyy';
        $sm2 = new RtSm2('base64');
        $encryptKey = bin2hex(base64_decode($encryptKey));
       // $key = $sm2->doDecrypt($encryptKey,$privateKey);
       $key = $sm2->doDecrypt($encryptKey,$privateKey,true,C1C2C3);

接口一直报错

Curve curve(115792089210356248756420345214020892766250353991924191454421193933289684991996, 18505919022281880113072981827955639221458448578012075254857346196103069175443, 115792089210356248756420345214020892766250353991924191454421193933289684991999) does not contain point (21923053047652226176961851790060037222566785743409165246174894147982436287639, 39907748959544104344143362640424285560854604783498473140463705333263725731102)

我补充下对方提供的java demo


    /**
     * 数字信封解密(SM4+SM2)
     * @param wDigEvp
     * @param encContents
     * @return
     */
    public static String[] decrypt(String privateKey, String wDigEvp, String... encContents) throws Exception {
        BCECPrivateKey bcecPrivateKey = GmUtil.getPrivatekeyFromD(new BigInteger(privateKey, 16));

        //SM2解密对称密钥
        byte[] wDigEvpByt = org.bouncycastle.util.encoders.Base64.decode(wDigEvp);
        System.out.println("HEX DEBUG:" + new String(Hex.encode(wDigEvpByt)));
        byte[] wOriKeyByt = sm2DecryptOld(cipherAsn1ToRaw(wDigEvpByt), bcecPrivateKey);

        System.out.println("解密出来的明文SM4密钥:" + new String(wOriKeyByt));
        System.out.println("解密出来的明文SM4密钥长度:" + wOriKeyByt.length);

        String[] contents = new String[encContents.length];
        for (int i = 0; i < encContents.length; i ++) {
            String wEcnTxt2 = encContents[i];
            byte wEncByt2[] = org.bouncycastle.util.encoders.Base64.decode(wEcnTxt2);

            byte[] bs = new byte[16];
            System.arraycopy(wOriKeyByt, 0, bs, 0, 16);
            System.out.println("解密出来的明文SM4密钥:" + new String(bs));
            //SM4解密
            byte[] wOriTxtByt = sm4DecryptCBC(bs, wEncByt2, SM4_CBC_IV);
            System.out.println("解密出来的明文:" + new String(wOriTxtByt));
        }
        return contents;
    }

 public static BCECPrivateKey getPrivatekeyFromD(BigInteger d) {
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d,
                ecParameterSpec);
        return new BCECPrivateKey("EC", ecPrivateKeySpec,
                BouncyCastleProvider.CONFIGURATION);
    }

   /**
     * c1||c2||c3
     *
     * @param data
     * @param key
     * @return
     */
    public static byte[] sm2DecryptOld(byte[] data, PrivateKey key) {
        BCECPrivateKey localECPrivateKey = (BCECPrivateKey) key;
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(
                localECPrivateKey.getD(), ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, ecPrivateKeyParameters);
        try {
            return sm2Engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }
lpilp commented 1 year ago

你这有两个问题: 1.这里如果构造的时候加了base64, 下面一行就不要再转成hex, 直接使用,

$sm2 = new RtSm2(); 
$encryptKey = bin2hex(base64_decode($encryptKey));
$key = $sm2->doDecrypt($encryptKey,$privateKey);
// 或者
$sm2 = new RtSm2('base64');
$key = $sm2->doDecrypt($encryptKey,$privateKey); 

问题2: encryptKey 正常情况应该是 c1c3c2, 但招行就喜欢和人不一样, 给的是 asn1(c1x,c1y, c3,c2), 我这个函数暂还不支持这个,你只要解开再拼成 c1c3c2的形式就行了, 最后解开后是密码是 “b0xxxxxxxxxxxc8”的16字节明文,

lpilp commented 1 year ago

觉得好的话,给颗星吧 ^_^

sjp325 commented 1 year ago

bingo,解开密码是对的。你说的 解开再拼成c1c3c2 ,这部要如何解开,指点一下

lpilp commented 1 year ago

https://github.com/fgrosse/PHPASN1 项目里自带着这个,可以看看上面的用法,解开

lpilp commented 1 year ago

你自己封装下吧

use FG\ASN1\ASNObject;

$data = 'MHgxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyKOw=';

$binaryData = base64_decode($data);        
$asnObject = ASNObject::fromBinary($binaryData);
$result = array();
foreach($asnObject as $object){
    $result[] = $object->__toString();
}
$c1x = gmp_strval(gmp_init($result[0],10),16);
$c1y = gmp_strval(gmp_init($result[1],10),16);
$c1x = padding_zero($c1x);
$c1y = padding_zero($c1y);

$c3 = $result[2];
$c2 = $result[3];
$ret = strtolower("{$c1x}{$c1y}{$c3}{$c2}");

function padding_zero($hex,$len = 64) {
    $left = $len - strlen($hex) ;
    if($left >0){
        $hex = str_repeat('0',$left) . $hex;
    }
    return $hex;
}
sjp325 commented 1 year ago

太谢谢大佬了,万分感谢

zhiYu2017 commented 10 months ago

@sjp325 我遇到的问题和你恰恰相反,对接招行付款码支付,需要对终端信息进行 加密,并且对加密私钥打包数字信封。验签校验一直过不了,你有没有做过类似的功能?

tianwenlike commented 8 months ago

我也遇到了,但是是加密前的问题,第三方用的是数字信封的一整套,sm2加密的格式和他们的不同,第三方用java结构是ASN.1的加密的结果不满足,请问大佬有没有办法解决,看到上面只有解密的,有没有c1+c3+c2编译成ASN.1的方法

tianwenlike commented 8 months ago

他们的加密规则是ASN.1的和原来的c1+c3+c2不一样 image

lpilp commented 8 months ago

@tianwenlike 这个该项目的简化版本里添加了相关的方法, https://github.com/lpilp/simplesm2/blob/main/src/smecc/SPLSM2/Sm2Asn1.php 里的 asn1_cccc($c1x, $c1y, $c3, $c2, $outFormat = 'hex') , 这个就是把 c1,c2,c3 打包成 asn1的, 如果要解开,请使用 里面有deoce函数就可以了 ps: c1 = c1x.C1y , hex的c1的话,就是前面64字节是 c1x, 后面的是c1y