Files
jsowell-charger-web/jsowell-common/src/main/java/com/jsowell/common/util/Sm2Util.java
2024-05-13 11:00:16 +08:00

285 lines
14 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String> 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;
}
}
}