lpilp / phpsm2sm3sm4

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

php生成的签名,java验签不通过 #95

Closed branll closed 2 weeks ago

branll commented 2 weeks ago

问题描述

开发环境: windows 测试环境密钥 将字符串,先sm3再sm2签名,调用java的接口,验签失败

麻烦给看看

php签名串示例

MEQCIAMsOvIyroeEEe+AY2GUjrJiI8jcAvTLhnyHFKq60BsqAiA57A7oMWDAVqGnHauno32/6fjAvkITtnGIwpaImNt3qQ==

java签名串示例 NzdiZTQ4NTI1YjBmZDJiMWQyYjk2MGY3MmU4YTRkMGU0ZDAzNDZjMGY4Mzk0NjQ3MmMyMmRhZGM5Y2U4NzgyNjY4Zjk3NmY2MTgwYjQ2NDc0ZGU3MDNlOGE2NjZiNWIzMmIxNDc2NGY3ZDBmOTEzNWUxYTUzNDQwZTAyN2IxZGM=

// 公钥
$publicKey = '04b71dd6c02e40811f6c07eada73cebb6c859cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1689543003f4ee0a7b7eb025010f';
// 私钥
$privateKey = '00abe5f233ff8e8423c09xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3f2775b66a25cf4fd1c6da3';
// 签名体
$plainText= 'version=1.0&appId=111111111111111&serial=1730789343715&body={"activityId":"12345678","userId":"W_wwwwwwwww"}';
// SM3对签名体进行摘要计算
$signatureSm3 = (new RtSm3())->digest($plainText);
// SM2算法使用私钥对摘要信息进行加密
$signature = (new RtSm2('base64', false))->doSign($signatureSm3, $privateKey);
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version> 5.8.0</version>
</dependency>

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import java.util.HashMap;
import java.util.Map;
import org.bouncycastle.crypto.engines.SM2Engine.Mode;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;

public class Sm2WithSm3SignUtil {
    public Sm2WithSm3SignUtil() {
    }

    public static String sm2WithSm3Sign(String plainText, String privateKey) {
        String encrypt = null;

        try {
            ECPrivateKeyParameters privateKeyParameters = BCUtil.toSm2Params(privateKey);
            SM2 sm2 = new SM2(privateKeyParameters, (ECPublicKeyParameters)null);
            sm2.usePlainEncoding();
            sm2.setMode(Mode.C1C2C3);
            byte[] sign = sm2.sign(SmUtil.sm3(plainText).getBytes(), (byte[])null);
            encrypt = HexUtil.encodeHexStr(sign);
        } catch (Exception var6) {
            var6.printStackTrace();
        }

        return Base64Encoder.encode(encrypt.getBytes());
    }

    public static boolean sm2WithSm3Verify(String data, String publicKey, String sign) {
        boolean flag = false;

        try {
            byte[] decode = Base64Decoder.decode(sign.getBytes());
            flag = decrypt(SmUtil.sm3(data), publicKey, new String(decode));
        } catch (Exception var5) {
            var5.printStackTrace();
        }

        return flag;
    }

    public static boolean decrypt(String data, String publicKey, String sign) {
        if (publicKey.length() == 130) {
            publicKey = publicKey.substring(2);
        }

        String xhex = publicKey.substring(0, 64);
        String yhex = publicKey.substring(64, 128);
        ECPublicKeyParameters ecPublicKeyParameters = BCUtil.toSm2Params(xhex, yhex);
        SM2 sm2 = new SM2((ECPrivateKeyParameters)null, ecPublicKeyParameters);
        sm2.usePlainEncoding();
        sm2.setMode(Mode.C1C2C3);
        boolean verify = sm2.verify(data.getBytes(), HexUtil.decodeHex(sign));
        return verify;
    }
}
lpilp commented 2 weeks ago

签名格式的问题, 国密签名会生成两个字段 r ,s ; PHP生成的是 标准的 base64_encode(asn1(r,s)), 你的这个java生成的是 base_encode(bin2hex(r+s)) 请自行解开, 在 util/SmSignFormatRS 中可以将asn1转出 r+s的,请自行按需求进行更新,格式转换

branll commented 2 weeks ago

试了一下,验签通过,非常感谢

// SM3对签名体进行摘要计算
$signatureSm3 = (new RtSm3())->digest($plainText);
$sm2Lib = new RtSm2('base64', false);
// SM2算法使用私钥对摘要信息进行加密
$signatureASN1 = $sm2Lib->doSign($signatureSm3, $privateKey);

// 居然还要这么操作来转换格式
$smSignFormatRS = new \Rtgm\util\SmSignFormatRS();
// 转换为RS
$signatureRS = $smSignFormatRS->asn1_to_rs($signatureASN1);
$signature = base64_encode(bin2hex(base64_decode($signatureRS)));
// 对方 验签通过

// 我方 验证签名
$verifyRS = base64_decode($signature);
// 转换为ASN1
$verifyASN1 = $smSignFormatRS->rs_to_asn1($verifyRS, 'hex');
$verifyRes = $sm2Lib->verifySign($signatureSm3, $verifyASN1, $publicKey);
// 我方 验签通过