参考文档:          

package com.cloudtravel.common.smencrypt;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Objects;

/**
 * @description: SM2加密算法--非对称加密
 * SM2椭圆曲线公钥密码算法是我国自主涉及的公钥密码算法,包括SM2-1椭圆曲线数字签名算法,SM2-2椭圆曲线密钥交换协议,
 * SM2-3椭圆曲线公钥加密算法.分别用于实现数据签名密钥协商和数据加密等功能 .
 * SM2和RSA算法的区别在于:SM2是基于椭圆曲线上点群离散对数难题 . 相对于RSA算法.256位的密码强度已经比RSA的2048位密码强度高
 * 一. ECC算法:SM2的基础
 * 1. 用户A选定一条适合加密的椭圆曲线Ep(a,b)(如y2=x3+ax+b).并取椭圆上一点.作为基点G
 * 2. 用户A选择一个私有密钥k,生成公开密钥(即公钥)K=kG
 * 3. A将Ep(a,b)和点K(即公钥),G传给用户B
 * 4. 用户B接到信息后,将待传输的明文(M)编码到椭圆Ep(a,b)上一点M,并产生一个随机整数r(r<n).开始加密
 * 5. 用户B计算点C1 = M+rK . C2 = rG , 并将C1,C2传给用户A
 * 6. 用户A接收到C1 & C2 . 计算C1-kC2 . 即 M+rK-k(rG) = M + rK - r(kG:第二步中K = kG.)= M
 * 再对点M进行解码就可以得到明文
 * 二.构成
 * SM2包括了:总则 , 数据签名算法 , 密钥交换协议 , 公钥加密算法四个部分
 * @author: walker
 * @DATE: 005-2022/9/5
 */
@Slf4j
public class SM2Util {

    /**
     * SM2算法生成密钥对
     * @return
     */
    public static KeyPair generateSm2KeyPair() {
        try {
            //用于生成椭圆曲线(EC)域参数的参数集
            final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("prime256v1");
            //获取一个椭圆曲线类型的密钥对生成器
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC",new BouncyCastleProvider());
            //生成随机变量
            SecureRandom random = new SecureRandom();
            //使用SM2的算法区域初始化密钥生成器
            kpg.initialize(sm2Spec , random);
            //使用密钥对生成器生成密钥对
            KeyPair keyPair = kpg.generateKeyPair();
            return keyPair;
        }catch (Exception e) {
            log.error("SM2Utils==>Error:generateSm2KeyPair error, message = " + e.getMessage());
            return null;
        }
    }

    /**
     * 公钥加密
     * @param param
     * @param publicKey
     * @return
     */
    public static String encryptWithPublicKey(String param , String publicKey) {
        if(StringUtils.isNotBlank(param) && StringUtils.isNotBlank(publicKey)) {
            byte [] data = param.getBytes(StandardCharsets.UTF_8);
            byte [] publicKeyArr = Base64.getDecoder().decode(publicKey);
            byte [] res = encryptWithPublicKey(data , publicKeyArr);
            return Base64.getEncoder().encodeToString(res);
        }else {
            log.error("SM2Utils ==> encryptWithPublicKey error : param = {} , publicKey = {}" , param , publicKey);
        }
        return null;
    }

    /**
     * SM2公钥加密
     * @param data 参数
     * @param key 公钥
     * @return
     */
    public static byte[] encryptWithPublicKey(byte [] data , byte [] key) {
        try {
            //公钥的ASN.1编码 , 用来初始化公钥 , 对公钥编码转换
            KeySpec keySpec = new X509EncodedKeySpec(key);
            PublicKey publicKey = KeyFactory.getInstance("EC", new BouncyCastleProvider())
                    .generatePublic(keySpec);
            ECPublicKeyParameters parameters = (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
            CipherParameters publicKeyParameters = new ParametersWithRandom(parameters);
            SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C2C3);
            engine.init(true , publicKeyParameters);
            return engine.processBlock(data , 0 , data.length);
        }catch (Exception e) {
            log.error("SM2Utils==>encrypt error: data = {} , key = {} , msg = {}" , data , key , e.getMessage());
        }
        return null;
    }


    /**
     * 私钥解密
     * @param cipherText 加密后的密文
     * @param privateKey 私钥
     * @return
     */
    public static String decryptWithPrivate(String cipherText , String privateKey) {
        if(StringUtils.isNotBlank(cipherText) && StringUtils.isNotBlank(privateKey)){
            //这里使用Base64.getDecoder().decode是为了和加密时返回那里对称
            byte [] cipherBytes = Base64.getDecoder().decode(cipherText);
            byte [] privateKeyBytes = Base64.getDecoder().decode(privateKey);
            byte [] paramText = decryptWithPrivate(cipherBytes , privateKeyBytes);
            //因为加密时入参就是param.getBytes(xxx).这里和加密对称
            return new String(paramText , StandardCharsets.UTF_8);
        }else {
            log.error("SM2Utils==>decryptWithPrivate error: param is illegal. cipherText={} , privateKey = {}",
                    cipherText , privateKey);
        }
        return null;
    }

    /**
     * 私钥解密
     * @param data 密文
     * @param key 私钥
     * @return
     */
    public static byte [] decryptWithPrivate(byte [] data , byte [] key) {
        try {
            //私钥的ASN.1编码 . 用来初始化私钥
            KeySpec keySpec = new PKCS8EncodedKeySpec(key);
            KeyFactory keyFactory = KeyFactory.getInstance("EC" , new BouncyCastleProvider());
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
            CipherParameters privateParameters = ECUtil.generatePrivateKeyParameter(privateKey);
            //MODE需要和加密时选择一致
            SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C2C3);
            engine.init(false , privateParameters);
            return engine.processBlock(data , 0 , data.length);
        }catch (Exception e){
            log.error("SM2Utils==>decrypt error: data = {} , key = {} , msg = {}" , data , key , e.getMessage());
        }
        return null;
    }

    /**
     * 通过私钥签名
     * @param param 参数
     * @param privateKey 私钥
     * @return
     */
    public static String signWithPrivateKey(String param, String privateKey) {
        byte [] paramBytes = param.getBytes(StandardCharsets.UTF_8);
        byte [] privateKeyBytes = Base64.getDecoder().decode(privateKey);
        byte [] signature = signWithPrivateKey(paramBytes , privateKeyBytes);
        return Base64.getEncoder().encodeToString(signature);
    }

    /**
     * 通过私钥签名
     * @param data data
     * @param key 私钥
     * @return
     */
    public static byte [] signWithPrivateKey(byte [] data , byte [] key) {
        try {
            SM2Signer signer = new SM2Signer();
            KeySpec keySpec = new PKCS8EncodedKeySpec(key);
            KeyFactory keyFactory = KeyFactory.getInstance("EC" , new BouncyCastleProvider());
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
            ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
            signer.init(true , privateKeyParameters);
            signer.update(data , 0 , data.length);
            return signer.generateSignature();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     *
     * @param param
     * @param signature
     * @param publicKey
     * @return
     */
    public static boolean verifyWithPublicKey(String param ,
                                              String signature ,
                                              String publicKey) {
        byte [] paramBytes = param.getBytes(StandardCharsets.UTF_8);
        byte [] signatureBytes = Base64.getDecoder().decode(signature);
        byte [] key = Base64.getDecoder().decode(publicKey);
        return verifyWithPublicKey(paramBytes , signatureBytes , key);
    }

    /**
     * 通过公钥验签
     * @param data
     * @param sign
     * @param key
     * @return
     */
    public static boolean verifyWithPublicKey(byte [] data , byte [] sign , byte [] key) {
        try {
            SM2Signer sm2Signer = new SM2Signer();
            KeySpec keySpec = new X509EncodedKeySpec(key);
            PublicKey publicKey = KeyFactory.getInstance("EC" , new BouncyCastleProvider())
                    .generatePublic(keySpec);
            CipherParameters parameters = ECUtil.generatePublicKeyParameter(publicKey);
            sm2Signer.init(false , parameters);
            sm2Signer.update(data , 0 , data.length);
            return sm2Signer.verifySignature(sign);
        }catch (Exception e) {
            log.error("SM2Utils==>verify error: data = {} , key = {} , msg = {}" , data , key , e.getMessage());
        }
        return false;
    }

    public static void main(String[] args)throws Throwable {
        //test---生成密钥对
        KeyPair keyPair = generateSm2KeyPair();
        String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
        System.out.println("SM2公钥为 = " + publicKey);
        System.out.println("SM2私钥为 = " + privateKey);
        String testData = "生活加油";
        System.out.println("当前参数 = " + testData);

        String signature = Base64.getEncoder().encodeToString(
                signWithPrivateKey(testData.getBytes(StandardCharsets.UTF_8) ,
                        Base64.getDecoder().decode(privateKey)
                )
        );
        String signature2 = signWithPrivateKey(testData , privateKey);
        System.out.println("私钥生成签名1= " + signature);
        System.out.println("私钥生成签名2= " + signature2);

        String cipherTxt = Base64.getEncoder().encodeToString(
                encryptWithPublicKey(testData.getBytes(StandardCharsets.UTF_8) ,
                        Base64.getDecoder().decode(publicKey)));
        String cipherTxt2 = encryptWithPublicKey(testData , publicKey);
        System.out.println("公钥加密后 = " + cipherTxt);
        System.out.println("公钥加密后2 = " + cipherTxt2);

        boolean verifyRes = verifyWithPublicKey(testData.getBytes(StandardCharsets.UTF_8) ,
                Base64.getDecoder().decode(signature),
                Base64.getDecoder().decode(publicKey));
        boolean verifyRes2 = verifyWithPublicKey(testData , signature , publicKey);
        boolean verifyRes3 = verifyWithPublicKey(testData , signature2 , publicKey);
        System.out.println("验签结果 = " + verifyRes);
        System.out.println("验签结果2 = " + verifyRes2);
        System.out.println("验签结果3 = " + verifyRes3);


        String decryptRes = new String(
                Objects.requireNonNull(decryptWithPrivate(
                        Base64.getDecoder().decode(cipherTxt) ,
                        Base64.getDecoder().decode(privateKey)) , "加密结果不为空"), StandardCharsets.UTF_8);
        String decryptRes2 = decryptWithPrivate(cipherTxt , privateKey);
        String decryptRes3 = decryptWithPrivate(cipherTxt2 , privateKey);
        System.out.println("解密后结果 = " + decryptRes);
        System.out.println("解密后结果2 = " + decryptRes2);
        System.out.println("解密后结果3 = " + decryptRes3);
    }
}