【Java】SM2Utils(国密 SM2 工具类)

发布时间 2023-08-25 10:35:24作者: XKIND

基于 bouncycastle 实现 国密 SM2

<!-- 引入 bouncycastle -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECNamedDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.KeyPairGeneratorSpi;
import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Base64;

import static java.util.Objects.isNull;

@Slf4j
public class SM2Utils {
    private static final String EC = "EC";
    private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder();
    private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();
    private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider();

    static {
        if (isNull(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME))) {
            Security.addProvider(PROVIDER);
        }
    }

    // region generateKeyPair

    /**
     * 获取sm2密钥对
     * BC库使用的公钥=64个字节+1个字节(04标志位),BC库使用的私钥=32个字节
     * SM2秘钥的组成部分有 私钥D 、公钥X 、 公钥Y , 他们都可以用长度为64的16进制的HEX串表示,
     * <br/>SM2公钥并不是直接由X+Y表示 , 而是额外添加了一个头
     *
     * @return
     */
    public static SM2KeyPair<byte[], BigInteger> genKeyPair() {
        return genKeyPair(false);
    }

    /**
     * 获取sm2密钥对
     * BC库使用的公钥=64个字节+1个字节(04标志位),BC库使用的私钥=32个字节
     * SM2秘钥的组成部分有 私钥D 、公钥X 、 公钥Y , 他们都可以用长度为64的16进制的HEX串表示,
     * <br/>SM2公钥并不是直接由X+Y表示 , 而是额外添加了一个头,当启用压缩时:公钥=有头+公钥X ,即省略了公钥Y的部分
     *
     * @param compressed 是否压缩公钥(加密解密都使用BC库才能使用压缩)
     * @return
     */
    @SneakyThrows
    public static SM2KeyPair<byte[], BigInteger> genKeyPair(boolean compressed) {
        //1.创建密钥生成器
        KeyPairGeneratorSpi.EC spi = new KeyPairGeneratorSpi.EC();
        //获取一条SM2曲线参数
        X9ECParameters parameters = GMNamedCurves.getByOID(GMObjectIdentifiers.sm2p256v1);
        //构造spec参数
        ECParameterSpec parameterSpec = new ECParameterSpec(parameters.getCurve(), parameters.getG(), parameters.getN());
        // SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG", "SUN");
        SecureRandom secureRandom = new SecureRandom();
        //2.初始化生成器,带上随机数
        spi.initialize(parameterSpec, secureRandom);
        //3.生成密钥对
        KeyPair asymmetricCipherKeyPair = spi.generateKeyPair();
        // 把公钥放入map中,默认压缩公钥
        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
        BCECPublicKey publicKeyParameters = (BCECPublicKey) asymmetricCipherKeyPair.getPublic();
        ECPoint ecPoint = publicKeyParameters.getQ();
        byte[] publicKey = ecPoint.getEncoded(compressed);
        // 把私钥放入map中
        BCECPrivateKey privateKeyParameters = (BCECPrivateKey) asymmetricCipherKeyPair.getPrivate();
        BigInteger intPrivateKey = privateKeyParameters.getD();
        return new SM2KeyPair<>(publicKey, intPrivateKey);
    }

    public static SM2KeyPair<String, String> genKeyPairAsHex() {
        return genKeyPairAsHex(false);
    }

    public static SM2KeyPair<String, String> genKeyPairAsHex(boolean compressed) {
        final SM2KeyPair<byte[], BigInteger> pair = genKeyPair(compressed);
        return new SM2KeyPair<>(
                Hex.toHexString(pair.getPublic()),
                pair.getPrivate().toString(16)
        );
    }

    public static SM2KeyPair<String, String> genKeyPairAsBase64() {
        return genKeyPairAsBase64(false);
    }

    public static SM2KeyPair<String, String> genKeyPairAsBase64(boolean compressed) {
        final SM2KeyPair<byte[], BigInteger> pair = genKeyPair(compressed);
        return new SM2KeyPair<>(
                BASE64_ENCODER.encodeToString(pair.getPublic()),
                BASE64_ENCODER.encodeToString(pair.getPrivate().toByteArray())
        );
    }

    // endregion generateKeyPair

    // region encrypt

    /**
     * SM2加密算法
     *
     * @param publicKey 公钥
     * @param data      待加密的数据
     * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
     */
    public static byte[] encrypt(byte[] publicKey, byte[] data) {
        // 按国密排序标准加密
        return encrypt(publicKey, data, SM2Engine.Mode.C1C3C2);
    }

    /**
     * SM2加密算法
     *
     * @param publicKey 公钥
     * @param data      待加密的数据
     * @param mode      密文排列方式
     * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
     */
    @SneakyThrows
    public static byte[] encrypt(byte[] publicKey, byte[] data, SM2Engine.Mode mode) {
        final ASN1ObjectIdentifier sm2p256v1 = GMObjectIdentifiers.sm2p256v1;
        // 获取一条SM2曲线参数
        X9ECParameters parameters = GMNamedCurves.getByOID(sm2p256v1);
        // 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N
        ECNamedDomainParameters namedDomainParameters = new ECNamedDomainParameters(
                sm2p256v1, parameters.getCurve(), parameters.getG(), parameters.getN());
        //提取公钥点
        ECPoint pukPoint = parameters.getCurve().decodePoint(publicKey);
        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, namedDomainParameters);
        SM2Engine sm2Engine = new SM2Engine(mode);
        SecureRandom secureRandom = new SecureRandom();
        // 设置sm2为加密模式
        sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, secureRandom));
        final byte[] encrypt = sm2Engine.processBlock(data, 0, data.length);
//        if (encrypt[0] == 0x04) {
//            return Arrays.copyOfRange(encrypt, 1, encrypt.length);
//        }
        return encrypt;
    }

    public static String encryptHex(String publicKey, String data) {
        return encryptHex(publicKey, data, SM2Engine.Mode.C1C3C2);
    }

    public static String encryptHex(String publicKey, String data, SM2Engine.Mode mode) {
        final byte[] key = Hex.decode(publicKey);
        byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
        final byte[] encrypt = encrypt(key, bytes, mode);
        return Hex.toHexString(encrypt);
    }

    public static String encryptBase64(String publicKey, String data) {
        return encryptBase64(publicKey, data, SM2Engine.Mode.C1C3C2);
    }

    public static String encryptBase64(String publicKey, String data, SM2Engine.Mode mode) {
        final byte[] key = BASE64_DECODER.decode(publicKey);
        byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
        final byte[] encrypt = encrypt(key, bytes, mode);
        return BASE64_ENCODER.encodeToString(encrypt);
    }

    // endregion encrypt

    // region decrypt

    /**
     * SM2解密算法
     *
     * @param privateKey 私钥
     * @param cipherData 密文数据
     * @return
     */
    public static byte[] decrypt(BigInteger privateKey, byte[] cipherData) {
        // 按国密排序标准解密
        return decrypt(privateKey, cipherData, SM2Engine.Mode.C1C3C2);
    }

    /**
     * SM2解密算法
     *
     * @param privateKey 私钥
     * @param cipherData 密文数据
     * @param mode       密文排列方式
     * @return
     */
    @SneakyThrows
    public static byte[] decrypt(BigInteger privateKey, byte[] cipherData, SM2Engine.Mode mode) {
        final ASN1ObjectIdentifier sm2p256v1 = GMObjectIdentifiers.sm2p256v1;
        //获取一条SM2曲线参数
        X9ECParameters parameters = GMNamedCurves.getByOID(sm2p256v1);
        // 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N
        ECNamedDomainParameters namedDomainParameters = new ECNamedDomainParameters(
                sm2p256v1, parameters.getCurve(), parameters.getG(), parameters.getN());
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKey, namedDomainParameters);
        SM2Engine sm2Engine = new SM2Engine(mode);
        // 设置sm2为解密模式
        sm2Engine.init(false, privateKeyParameters);
        // 使用BC库加解密时密文以04开头,传入的密文前面没有04则补上
        if (cipherData[0] == 0x04) {
            return sm2Engine.processBlock(cipherData, 0, cipherData.length);
        } else {
            byte[] bytes = new byte[cipherData.length + 1];
            bytes[0] = 0x04;
            System.arraycopy(cipherData, 0, bytes, 1, cipherData.length);
            return sm2Engine.processBlock(bytes, 0, bytes.length);
        }
    }

    public static String decryptHex(String privateKey, String cipherData) {
        return decryptHex(privateKey, cipherData, SM2Engine.Mode.C1C3C2);
    }

    public static String decryptHex(String privateKey, String cipherData, SM2Engine.Mode mode) {
        final BigInteger key = new BigInteger(privateKey, 16);
        final byte[] decrypt = decrypt(key, Hex.decode(cipherData), mode);
        return new String(decrypt, StandardCharsets.UTF_8);
    }

    public static String decryptBase64(String privateKey, String cipherData) {
        return decryptBase64(privateKey, cipherData, SM2Engine.Mode.C1C3C2);
    }

    public static String decryptBase64(String privateKey, String cipherData, SM2Engine.Mode mode) {
        final BigInteger key = new BigInteger(BASE64_DECODER.decode(privateKey));
        final byte[] decrypt = decrypt(key, BASE64_DECODER.decode(cipherData), mode);
        return new String(decrypt, StandardCharsets.UTF_8);
    }

    // endregion decrypt

    // region sign & cert

    /**
     * 签名
     *
     * @param plainText  待签名文本
     * @param privateKey 私钥
     * @return
     * @throws GeneralSecurityException
     */
    public static String sign(String plainText, BigInteger privateKey) throws GeneralSecurityException {
        X9ECParameters parameters = GMNamedCurves.getByOID(GMObjectIdentifiers.sm2p256v1);
        ECParameterSpec parameterSpec = new ECParameterSpec(parameters.getCurve(), parameters.getG(), parameters.getN());
        ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(privateKey, parameterSpec);
        PrivateKey bcecPrivateKey = new BCECPrivateKey(EC, privateKeySpec, BouncyCastleProvider.CONFIGURATION);
        // 创建签名对象
        Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), PROVIDER);
        // 初始化为签名状态
        signature.initSign(bcecPrivateKey);
        // 传入签名字节
        signature.update(plainText.getBytes(StandardCharsets.UTF_8));
        // 签名
        return BASE64_ENCODER.encodeToString(signature.sign());
    }

    /**
     * 验签
     *
     * @param plainText 待签名文本
     * @param signText
     * @param publicKey 公钥
     * @return
     * @throws GeneralSecurityException
     */
    public static boolean verify(String plainText, String signText, byte[] publicKey) throws GeneralSecurityException {
        X9ECParameters parameters = GMNamedCurves.getByOID(GMObjectIdentifiers.sm2p256v1);
        ECParameterSpec parameterSpec = new ECParameterSpec(parameters.getCurve(), parameters.getG(), parameters.getN());
        ECPoint ecPoint = parameters.getCurve().decodePoint(publicKey);
        ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(ecPoint, parameterSpec);
        PublicKey bcecPublicKey = new BCECPublicKey(EC, publicKeySpec, BouncyCastleProvider.CONFIGURATION);
        // 创建签名对象
        Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), PROVIDER);
        // 初始化为验签状态
        signature.initVerify(bcecPublicKey);
        signature.update(plainText.getBytes(StandardCharsets.UTF_8));
        return signature.verify(BASE64_DECODER.decode(signText));
    }

    /**
     * 证书验签
     *
     * @param certText  证书串
     * @param plainText 签名原文
     * @param signText  签名产生签名值 此处的签名值实际上就是 R和S的sequence
     * @return
     * @throws GeneralSecurityException
     */
    public static boolean certVerify(String certText, String plainText, String signText) throws GeneralSecurityException {
        // 解析证书
        CertificateFactory factory = new CertificateFactory();
        X509Certificate certificate = (X509Certificate) factory.engineGenerateCertificate(
                new ByteArrayInputStream(BASE64_DECODER.decode(certText)));
        // 验证签名
        Signature signature = Signature.getInstance(certificate.getSigAlgName(), PROVIDER);
        signature.initVerify(certificate);
        signature.update(plainText.getBytes(StandardCharsets.UTF_8));
        return signature.verify(BASE64_DECODER.decode(signText));
    }

    // endregion sign & cert

    public static void main(String[] args) {
//        final String plainText = "123456";

//        final SM2KeyPair<String, String> keys = genKeyPairAsBase64();
//        log.debug("\n公钥 : {}\n私钥 : {}", keys.getPublic(), keys.getPrivate());

//        String pubKey = "04a445fa8aa9318a2e4f2d0fd718fafc6443f408c805e51979679840907c6ae56e4e3378382f627165bbbb2566dd301d6695b0c7d6192177b5ef8b7561547d7cc5";
//        String priKey = "f2ad7ce861f362caf026725b3e9558c5477e7e0f55a476b1a2200d43425a0e1b";

//        String pubKey = "BPaIW/Bdy1brZeCvaXU95SYRbvT8O/A3cC67Nm8v2ukSikG6ToBJ8yX3rDzg48+R0qimVnN3QVgiAhS2aPprHNA=";
//        String priKey = "T768XF7KJXwKdeHRetcmBwiDczSgxIDBj3ioP9ozWG4=";

//        String pubKey = "BDIVcRKDxr0eTLHs1kjRw5UXcGtVZAQJPJ7H+dwiQt/Rfywi+GKkl7YtJgZvjOpQd9WuoIqfclgfnMxJV2R6Wlk=";
//        String priKey = "eb5wex++fwJ252auYDmDyCtGDHf+CaSXVXvX1uid4AY=";

//        String encrypt = encryptBase64(pubKey, plainText);
//        String decrypt = decryptBase64(priKey, encrypt);
//        log.debug("\n加密 : {}\n解密 : {}", encrypt, decrypt);

//        try {
//            String sign = sign(plainText, new BigInteger(BASE64_DECODER.decode(priKey)));
//            boolean verify = verify(plainText, sign, BASE64_DECODER.decode(pubKey));
//            log.debug("\n签名 : {}\n验签 : {}", sign, verify);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
    }
public class SM2KeyPair<U, V> {
    protected V privateKey;
    protected U publicKey;

    public SM2KeyPair(U publicKey, V privateKey) {
        this.publicKey = publicKey;
        this.privateKey = privateKey;
    }

    public U getPublic() {
        return publicKey;
    }

    public V getPrivate() {
        return privateKey;
    }
}