package com.jsowell.common.util; import com.alibaba.fastjson2.JSON; import com.jsowell.common.util.http.HttpUtils; 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.ArrayList; import java.util.Collections; import java.util.List; 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 String getSignContent(JSONObject rawData) { StringBuilder content = new StringBuilder(); List keys = new ArrayList<>(rawData.keySet()); // 将参数集合排序 Collections.sort(keys); for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = (String) rawData.get(key); // 拼装所有非空参数 if (key != null && !"".equalsIgnoreCase(key) && value != null && !"".equalsIgnoreCase(value)) { content.append(i == 0 ? "" : "&").append(key).append("=").append(value); } } return content.toString(); } 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","202212231641900215435"); 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); String recordInfoStr = generateEncryptedRequestInfo(data, platformPublicKeyStr, thirdPartyPrivateKeyStr); String tpToken = "4121d1c9121a41d894ed5082d264db30"; // 发送请求 String url = "http://123.60.34.253:9527/parking-shop/tp/chargeRecord/report"; JSONObject postParam = new JSONObject(); postParam.put("recordInfo", recordInfoStr); String result = HttpUtils.sendPostTpToken(url, postParam.toJSONString(), tpToken); // {"code":103,"msg":"数据验签不通过","data":null} // String result = HttpUtil.post(url, postParam.toJSONString()); System.out.println(result); } public static JSONObject getPlaintextRequestInfo(String encryptedString, String platformPrivateKeyStr, String thirdPartyPublicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { String plaintext = decrypt(platformPrivateKeyStr, encryptedString); JSONObject result = JSON.parseObject(plaintext); if(verify(thirdPartyPublicKeyStr, result)){ return result; }else{ return null; } } }