From 8e719f6bd78e7b538adfc9358fb02c816ab90ef6 Mon Sep 17 00:00:00 2001 From: Lemon Date: Tue, 7 May 2024 14:06:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E5=9B=BD=E5=AF=86?= =?UTF-8?q?=E4=BA=8C=E5=8A=A0=E5=AF=86=E6=96=B9=E5=BC=8FUtil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jsowell-common/pom.xml | 14 + .../java/com/jsowell/common/util/Sm2Util.java | 246 ++++++++++++++++++ 2 files changed, 260 insertions(+) create mode 100644 jsowell-common/src/main/java/com/jsowell/common/util/Sm2Util.java diff --git a/jsowell-common/pom.xml b/jsowell-common/pom.xml index 401a3605d..6cb2978a2 100644 --- a/jsowell-common/pom.xml +++ b/jsowell-common/pom.xml @@ -178,6 +178,20 @@ 3.10.2 + + Sm2Util + Sm2Util + 1.0.0 + + + + + org.bouncycastle + bcpg-jdk18on + 1.77 + + + diff --git a/jsowell-common/src/main/java/com/jsowell/common/util/Sm2Util.java b/jsowell-common/src/main/java/com/jsowell/common/util/Sm2Util.java new file mode 100644 index 000000000..61e0608df --- /dev/null +++ b/jsowell-common/src/main/java/com/jsowell/common/util/Sm2Util.java @@ -0,0 +1,246 @@ +package com.jsowell.common.util; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import com.alibaba.fastjson2.JSONObject; +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.spec.ECParameterSpec; +import org.bouncycastle.util.Strings; +import org.bouncycastle.util.encoders.Base64; + +import java.security.*; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.TreeMap; + +/** + * 国密二加密 Util + * + * @author Lemon + * @Date 2024/5/7 10:23:56 + */ +public class Sm2Util { + + static{ + Security.addProvider(new BouncyCastleProvider()); + } + + /** + * 随机生成sm2的公钥私钥对 + * @return 公钥私钥对 + */ + public static KeyPair generateSm2KeyPair() { + try { + final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1"); + // 获取一个椭圆曲线类型的密钥对生成器 + 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) { + e.printStackTrace(); + return null; + } + } + + /** + * + * @param data 加密前的json数据 + * @param platformPublicKeyStr 点行平台的国密二公钥 + * @param thirdPartyPrivateKeyStr 第三方平台生成的国密二私钥 + * @return 用于http发送请求的数据 + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws NoSuchProviderException + * @throws CryptoException + */ + public static String generateEncryptedRequestInfo(JSONObject data, String platformPublicKeyStr, String thirdPartyPrivateKeyStr) + throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, CryptoException { + String sign = sign(thirdPartyPrivateKeyStr, data); + data.put("sign",sign); + return encrypt(platformPublicKeyStr, data.toString()); + } + + /** + * + * @param publicKeyStr 以BASE64表示的sm2公钥 + * @param data 待加密字符串, UTF-8编码 + * @return 以BASE64表示的加密结果 + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws InvalidKeySpecException + */ + private static String encrypt(String publicKeyStr, String data) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException { + BCECPublicKey publicKey = getPublicKey(Base64.decode(publicKeyStr)); + //通过公钥对象获取公钥的基本域参数。 + 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(SM2Engine.Mode.C1C3C2); + //初始化加密引擎 + 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) { + e.printStackTrace(); + } + //将加密后的字节串转换为BASE 64字符串 + return Base64.toBase64String(arrayOfBytes); + } + + /** + * + * @param privateKeyStr 以BASE64表示的sm2私钥 + * @param cipherData 以BASE64表示的加密结果 + * @return 解密后字符串, UTF-8编码 + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws InvalidKeySpecException + */ + private static String decrypt(String privateKeyStr, String cipherData) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException { + BCECPrivateKey privateKey = getPrivateKey(Base64.decode(privateKeyStr)); + byte[] cipherDataByte = Base64.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(SM2Engine.Mode.C1C3C2); + 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) { + e.printStackTrace(); + } + return result; + } + + /** + * + * @param privateKeyStr 以BASE64表示的sm2私钥 + * @param dataJson 待签名json数据 + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws InvalidKeySpecException + * @throws CryptoException + */ + private static String sign(String privateKeyStr, JSONObject dataJson) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, CryptoException { + BCECPrivateKey privateKey = getPrivateKey(Base64.decode(privateKeyStr)); + ECParameterSpec ecParameterSpec = privateKey.getParameters(); + ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(), + ecParameterSpec.getG(), ecParameterSpec.getN()); + //通过私钥值和私钥钥基本参数创建私钥参数对象 + ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(), + ecDomainParameters); + + //创建签名实例 + SM2Signer sm2Signer = new SM2Signer(); + + //初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678 + try { + sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(ecPrivateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray("1234567812345678"))); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + String content = getSignContent(dataJson); + byte[] message = content.getBytes(); + sm2Signer.update(message, 0, message.length); + //生成签名,签名分为两部分r和s,分别对应索引0和1的数组 + byte[] signBytes = sm2Signer.generateSignature(); + return Base64.toBase64String(signBytes); + } + + /** + * + * @param publicKeyStr 以BASE64表示的sm2公钥 + * @param dataJson 含有签名的待验签json数据 + * @return + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws InvalidKeySpecException + */ + private static boolean verify(String publicKeyStr, JSONObject dataJson) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException { + BCECPublicKey publicKey = getPublicKey(Base64.decode(publicKeyStr)); + ECParameterSpec ecParameterSpec = publicKey.getParameters(); + ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(), + ecParameterSpec.getG(), ecParameterSpec.getN()); + //通过公钥值和公钥基本参数创建公钥参数对象 + ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters); + //创建签名实例 + SM2Signer sm2Signer = new SM2Signer(); + ParametersWithID parametersWithID = new ParametersWithID(ecPublicKeyParameters, Strings.toByteArray("1234567812345678")); + sm2Signer.init(false, parametersWithID); + String sign = (String) dataJson.remove("sign"); + String content = getSignContent(dataJson); + byte[] message = content.getBytes(); + sm2Signer.update(message, 0, message.length); + //验证签名结果 + return sm2Signer.verifySignature(Base64.decode(sign)); + } + + private static String getSignContent(JSONObject rawData){ + JSONObject data = new JSONObject(new TreeMap<>()); + rawData.forEach((k,v) -> data.put(k,v)); + StringBuffer sb = new StringBuffer(); + data.forEach((k,v)->{ + if (v != null && !"".equals(v)) { + sb.append(k + "=" + v + "&"); + } + }); + String stringData = sb.toString(); + return stringData == null || stringData.isEmpty() ? "" : stringData.substring(0,stringData.length()-1); + } + + private static BCECPrivateKey getPrivateKey(byte[] privateBytes) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException { + PKCS8EncodedKeySpec eks2 = new PKCS8EncodedKeySpec(privateBytes); + KeyFactory kf= KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); + return (BCECPrivateKey) kf.generatePrivate(eks2); + } + + private static BCECPublicKey getPublicKey(byte[] publicBytes) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException { + X509EncodedKeySpec eks = new X509EncodedKeySpec(publicBytes); + KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); + return (BCECPublicKey) kf.generatePublic(eks); + } + + public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, CryptoException { +// KeyPair keyPair = generateSm2KeyPair(); +// System.out.println(Base64.toBase64String(keyPair.getPublic().getEncoded())); +// System.out.println(Base64.toBase64String(keyPair.getPrivate().getEncoded())); + String platformPublicKeyStr = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEYPOlKmr/XY+na8KxiNvRui1esFugt4tT2AVk+eRlH4KCYLabDZDordal3kcn4UNM7t6J+dyhcfLstNWXpf4lQA=="; + String thirdPartyPrivateKeyStr = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgCMref1FGlPZ9RfeJw/cnU5uEvFZNhHt7OvF4sgXnBjWgCgYIKoEcz1UBgi2hRANCAARj6kqkCaeNJSxWExQFsot1OuSCFrQOblhKx0U/y8GhgSND2MOAM08yXzl308waLqLt+jcsLF2UTW6XfrZNS5pk"; + JSONObject data = new JSONObject(); + data.put("outTradeId","202212231641900215435329"); + data.put("plateNum","浙B12345"); + data.put("startTime","2023-01-09 10:00:01"); + data.put("endTime","2023-01-09 11:00:02"); + data.put("recordTime","2023-01-09 12:00:03"); + data.put("payAmount","12.34"); + System.out.println(data); + System.out.println(generateEncryptedRequestInfo(data,platformPublicKeyStr,thirdPartyPrivateKeyStr)); + } +}