简介
什么是SM2
SM2是国家密码管理局于2010年12月17日发布的椭圆曲线公钥密码算法。

SM2 算法和 RSA 算法都是公钥密码算法,SM2 算法是一种更先进安全的算法,在我们国家商用密码体系中被用来替换 RSA 算法。

随着密码技术和计算机技术的发展,目前常用的 1024 位 RSA 算法面临严重的安全威胁,我们国家密码管理部门经过研究,决定采用 SM2 椭圆曲线算法替换RSA算法。

SM2算法和RSA算法比较
SM2性能更优更安全:密码复杂度高、处理速度快、机器性能消耗更小

- SM2 RSA
算法结构 基本椭圆曲线(ECC) 基于特殊的可逆模幂运算
计算复杂度 完全指数级 亚指数级
存储空间 192-256bit 2048-4096bit
秘钥生成速度 较RSA算法快百倍以上
解密加密速度 较快 一般

POM依赖

<dependency>     <groupId>org.bouncycastle</groupId>     <artifactId>bcprov-ext-jdk15to18</artifactId>     <version>1.68</version> </dependency> <dependency>     <groupId>org.bouncycastle</groupId>     <artifactId>bcprov-jdk15to18</artifactId>     <version>1.68</version> </dependency>
package com.szsti.gasstation.utils;  import com.alibaba.fastjson.JSON; import org.bouncycastle.asn1.gm.GMNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.CryptoException; import org.bouncycastle.crypto.engines.SM2Engine; import org.bouncycastle.crypto.params.*; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; 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.Strings; import org.bouncycastle.util.encoders.Hex; import org.springframework.stereotype.Component;  import java.math.BigInteger; import java.security.*; import java.security.spec.ECGenParameterSpec; import java.util.Base64; import java.util.TreeMap;  /* 概念: SM1为对称加密。其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。 SM2为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。 SM3消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。 SM4无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。 ----------------------------------- java 国密算法sm2、sm3  */  public class Sm2 {     /**      * @return KeyPair      * @Description 生成秘钥对      * @Author msx      */     public static KeyPair createECKeyPair() {         //使用标准名称创建EC参数生成的参数规范         final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");          // 获取一个椭圆曲线类型的密钥对生成器         final KeyPairGenerator kpg;         try {             kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());             // 使用SM2算法域参数集初始化密钥生成器(默认使用以最高优先级安装的提供者的 SecureRandom 的实现作为随机源)             // kpg.initialize(sm2Spec);              // 使用SM2的算法域参数集和指定的随机源初始化密钥生成器             kpg.initialize(sm2Spec, new SecureRandom());              // 通过密钥生成器生成密钥对             return kpg.generateKeyPair();         } catch (Exception e) {             e.printStackTrace();             return null;         }     }      /**      * @Description 公钥加密      * @Author msx      * @param publicKeyHex SM2十六进制公钥      * @param data         明文数据      * @return String      */     public static String encrypt(String publicKeyHex, String data) {         return encrypt(getECPublicKeyByPublicKeyHex(publicKeyHex), data, 1);     }      /**      * @Description 公钥加密      * @Author msx      * @param publicKey SM2公钥      * @param data      明文数据      * @param modeType  加密模式      * @return String      */     public static String encrypt(BCECPublicKey publicKey, String data, int modeType) {         //加密模式         SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;         if (modeType != 1) {             mode = SM2Engine.Mode.C1C2C3;         }         //通过公钥对象获取公钥的基本域参数。         ECParameterSpec ecParameterSpec = publicKey.getParameters();         ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),                 ecParameterSpec.getG(), ecParameterSpec.getN());         //通过公钥值和公钥基本参数创建公钥参数对象         ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);         //根据加密模式实例化SM2公钥加密引擎         SM2Engine sm2Engine = new SM2Engine(mode);         //初始化加密引擎         sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));         byte[] arrayOfBytes = null;         try {             //将明文字符串转换为指定编码的字节串             byte[] in = data.getBytes("utf-8");             //通过加密引擎对字节数串行加密             arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);         } catch (Exception e) {             System.out.println("SM2加密时出现异常:" + e.getMessage());             e.printStackTrace();         }         //将加密后的字节串转换为十六进制字符串         return Hex.toHexString(arrayOfBytes);     }      /**      * @Description 私钥解密      * @Author msx      * @param privateKeyHex SM2十六进制私钥      * @param cipherData    密文数据      * @return String      */     public static String decrypt(String privateKeyHex, String cipherData) {         return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKeyHex), cipherData, 1);     }      /**      * @Description 私钥解密      * @Author msx      * @param privateKey SM私钥      * @param cipherData 密文数据      * @param modeType   解密模式      * @return      */     public static String decrypt(BCECPrivateKey privateKey, String cipherData, int modeType) {         //解密模式         SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;         if (modeType != 1)             mode = SM2Engine.Mode.C1C2C3;         //将十六进制字符串密文转换为字节数组(需要与加密一致,加密是:加密后的字节数组转换为了十六进制字符串)         byte[] cipherDataByte = Hex.decode(cipherData);         //通过私钥对象获取私钥的基本域参数。         ECParameterSpec ecParameterSpec = privateKey.getParameters();         ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),                 ecParameterSpec.getG(), ecParameterSpec.getN());         //通过私钥值和私钥钥基本参数创建私钥参数对象         ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),                 ecDomainParameters);         //通过解密模式创建解密引擎并初始化         SM2Engine sm2Engine = new SM2Engine(mode);         sm2Engine.init(false, ecPrivateKeyParameters);         String result = null;         try {             //通过解密引擎对密文字节串进行解密             byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);             //将解密后的字节串转换为utf8字符编码的字符串(需要与明文加密时字符串转换成字节串所指定的字符编码保持一致)             result = new String(arrayOfBytes, "utf-8");         } catch (Exception e) {             System.out.println("SM2解密时出现异常" + e.getMessage());         }         return result;     }     //椭圆曲线ECParameters ASN.1 结构     private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");     //椭圆曲线公钥或私钥的基本域参数。     private static ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());      /**      * @Description 公钥字符串转换为 BCECPublicKey 公钥对象      * @Author msx      * @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首个字节为0x04:表示该公钥为非压缩格式,操作时需要删除)      * @return BCECPublicKey SM2公钥对象      */     public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {         //截取64字节有效的SM2公钥(如果公钥首个字节为0x04)         if (pubKeyHex.length() > 128) {             pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);         }         //将公钥拆分为x,y分量(各32字节)         String stringX = pubKeyHex.substring(0, 64);         String stringY = pubKeyHex.substring(stringX.length());         //将公钥x、y分量转换为BigInteger类型         BigInteger x = new BigInteger(stringX, 16);         BigInteger y = new BigInteger(stringY, 16);         //通过公钥x、y分量创建椭圆曲线公钥规范         ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);         //通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)         return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);     }      /**      * @Description 私钥字符串转换为 BCECPrivateKey 私钥对象      * @Author msx      * @param privateKeyHex 32字节十六进制私钥字符串      * @return BCECPrivateKey SM2私钥对象      */     public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {         //将十六进制私钥字符串转换为BigInteger对象         BigInteger d = new BigInteger(privateKeyHex, 16);         //通过私钥和私钥域参数集创建椭圆曲线私钥规范         ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters);         //通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)         return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);     }      /**      * 私钥签名      * @param privateKey    私钥      * @param content       待签名内容      * @return      */     public static String sign(String privateKey, String content) throws CryptoException {         //待签名内容转为字节数组         byte[] message = Hex.decode(content);          //获取一条SM2曲线参数         X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");         //构造domain参数         ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),                 sm2ECParameters.getG(), sm2ECParameters.getN());          BigInteger privateKeyD = new BigInteger(privateKey, 16);         ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);          //创建签名实例         SM2Signer sm2Signer = new SM2Signer();          //初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678         try {             sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray("1234567812345678")));         } catch (NoSuchAlgorithmException e) {             System.out.println("签名时出现异常:"+e);         }         sm2Signer.update(message, 0, message.length);         //生成签名,签名分为两部分r和s,分别对应索引0和1的数组         byte[] signBytes = sm2Signer.generateSignature();          String sign = Hex.toHexString(signBytes);          return sign;     }       /**      * 验证签名      * @param publicKey     公钥      * @param content       待签名内容      * @param sign          签名值      * @return      */     public static boolean verify(String publicKey, String content, String sign) {         //待签名内容         byte[] message = Hex.decode(content);         byte[] signData = Hex.decode(sign);          // 获取一条SM2曲线参数         X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");         // 构造domain参数         ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),                 sm2ECParameters.getG(),                 sm2ECParameters.getN());         //提取公钥点         ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));         // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04         ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);         //创建签名实例         SM2Signer sm2Signer = new SM2Signer();         ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters, Strings.toByteArray("1234567812345678"));         sm2Signer.init(false, parametersWithID);         sm2Signer.update(message, 0, message.length);         //验证签名结果         boolean verify = sm2Signer.verifySignature(signData);         return verify;     }       public static void main(String[] args) throws Exception{         System.out.println("#########################start############################");         //公钥         String publicKeyHex = "";         //私钥         String privateKeyHex = "";         //生成公钥、私钥方法(公钥给前端进行加密,私钥后端进行数据解密、验签)         KeyPair keyPair = createECKeyPair();         PublicKey publicKey = keyPair.getPublic();         if (publicKey instanceof BCECPublicKey) {             //获取65字节非压缩缩的十六进制公钥串(0x04)             publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));             System.out.println("---->SM2公钥:" + publicKeyHex);         }         PrivateKey privateKey = keyPair.getPrivate();         if (privateKey instanceof BCECPrivateKey) {             //获取32字节十六进制私钥串             privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);             System.out.println("---->SM2私钥:" + privateKeyHex);         }          //加密数据         TreeMap<String, Object> params = new TreeMap<String, Object>();         params.put("alarmNum", "1000041");         params.put("alarmCode", "20101");         params.put("recordTime", "2020-06-30 09:05:31");          // RSA加密         String data = JSON.toJSONString(params);          //生成签名         String sign = "";         try {             sign = sign(privateKeyHex, Hex.toHexString(data.getBytes()));         } catch (CryptoException e) {             e.printStackTrace();         }         System.out.println("生成签名" + sign);          //签名验签         boolean verify = verify(publicKeyHex, Hex.toHexString(data.getBytes()), sign);         System.out.println("验签结果" + verify);          //公钥加密         //将十六进制公钥串转换为 BCECPublicKey 公钥对象         String encryptData = encrypt(publicKeyHex, data);         System.out.println("---->加密结果:" + encryptData);          //私钥解密         //将十六进制私钥串转换为 BCECPrivateKey 私钥对象         String dataNew = decrypt(privateKeyHex, encryptData);         System.out.println("---->解密结果:" + dataNew);           System.out.println("#########################end############################");     } }