|
|
@@ -0,0 +1,232 @@
|
|
|
+package com.usky.vpp.crypto;
|
|
|
+
|
|
|
+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.jcajce.provider.asymmetric.util.ECUtil;
|
|
|
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
|
+
|
|
|
+import cn.hutool.crypto.SmUtil;
|
|
|
+import cn.hutool.crypto.asymmetric.KeyType;
|
|
|
+import cn.hutool.crypto.asymmetric.SM2;
|
|
|
+
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.security.KeyFactory;
|
|
|
+import java.security.PrivateKey;
|
|
|
+import java.security.PublicKey;
|
|
|
+import java.security.SecureRandom;
|
|
|
+import java.security.Security;
|
|
|
+import java.security.Signature;
|
|
|
+import java.security.spec.PKCS8EncodedKeySpec;
|
|
|
+import java.security.spec.X509EncodedKeySpec;
|
|
|
+import java.util.Base64;
|
|
|
+
|
|
|
+import javax.crypto.Cipher;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 运管平台 UN/DN 国密工具(SM2 加解密 + SM3withSM2 签名/验签)。
|
|
|
+ * <p>加解密优先使用 BC {@code Cipher("SM2")}(与联调 SM3Util 一致,C1C2C3);签名/验签针对密文字节。</p>
|
|
|
+ */
|
|
|
+public final class VppUnSmCryptoUtil {
|
|
|
+
|
|
|
+ private static final String EC = "EC";
|
|
|
+ private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
|
|
|
+ private static final String SM2 = "SM2";
|
|
|
+ private static final String SIGNATURE_ALGORITHM = "SM3withSM2";
|
|
|
+
|
|
|
+ static {
|
|
|
+ if (Security.getProvider(BC) == null) {
|
|
|
+ Security.addProvider(new BouncyCastleProvider());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private VppUnSmCryptoUtil() {
|
|
|
+ }
|
|
|
+
|
|
|
+ public static final class EncryptedPayload {
|
|
|
+ private final String cipherBase64;
|
|
|
+ private final String signBase64;
|
|
|
+
|
|
|
+ public EncryptedPayload(String cipherBase64, String signBase64) {
|
|
|
+ this.cipherBase64 = cipherBase64;
|
|
|
+ this.signBase64 = signBase64;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getCipherBase64() {
|
|
|
+ return cipherBase64;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getSignBase64() {
|
|
|
+ return signBase64;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** DN 请求 UN:UN 公钥加密 + DN 私钥对密文字节签名。 */
|
|
|
+ public static EncryptedPayload encryptAndSignRequest(String plainJson,
|
|
|
+ String unPublicKeyBase64,
|
|
|
+ String dnPrivateKeyBase64) throws Exception {
|
|
|
+ byte[] cipherBytes = encryptByPublicKey(plainJson, unPublicKeyBase64);
|
|
|
+ String cipherBase64 = Base64.getEncoder().encodeToString(cipherBytes);
|
|
|
+ String signBase64 = signCipherBytes(cipherBytes, dnPrivateKeyBase64);
|
|
|
+ return new EncryptedPayload(cipherBase64, signBase64);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** DN 解析 UN 响应:UN 公钥验签密文字节 + DN 私钥解密。 */
|
|
|
+ public static String verifyAndDecryptResponse(String cipherBase64,
|
|
|
+ String signBase64,
|
|
|
+ String unPublicKeyBase64,
|
|
|
+ String dnPrivateKeyBase64) throws Exception {
|
|
|
+ byte[] cipherBytes = decodeBase64(cipherBase64);
|
|
|
+ if (!verifyCipherBytes(cipherBytes, signBase64, unPublicKeyBase64)) {
|
|
|
+ throw new IllegalStateException("sign verify fail");
|
|
|
+ }
|
|
|
+ return new String(decryptByPrivateKey(cipherBytes, dnPrivateKeyBase64), StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 仅 SM2 解密(已验签场景)。 */
|
|
|
+ public static String decryptByDnPrivateKey(String cipherBase64, String dnPrivateKeyBase64) throws Exception {
|
|
|
+ byte[] cipherBytes = decodeBase64(cipherBase64);
|
|
|
+ return new String(decryptByPrivateKey(cipherBytes, dnPrivateKeyBase64), StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String signCipherBytes(byte[] cipherBytes, String privateKeyBase64) throws Exception {
|
|
|
+ return sign(cipherBytes, privateKeyBase64);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static boolean verifyCipherBytes(byte[] cipherBytes, String signBase64, String publicKeyBase64)
|
|
|
+ throws Exception {
|
|
|
+ return verify(cipherBytes, publicKeyBase64, signBase64);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String sign(byte[] data, String privateKeyBase64) throws Exception {
|
|
|
+ try {
|
|
|
+ PrivateKey privateKey = loadPrivateKey(privateKeyBase64);
|
|
|
+ Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, BC);
|
|
|
+ signature.initSign(privateKey);
|
|
|
+ signature.update(data);
|
|
|
+ return Base64.getEncoder().encodeToString(signature.sign());
|
|
|
+ } catch (Exception ex) {
|
|
|
+ SM2 sm2 = SmUtil.sm2(privateKeyBase64.trim(), null);
|
|
|
+ return Base64.getEncoder().encodeToString(sm2.sign(data));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean verify(byte[] data, String publicKeyBase64, String signBase64) throws Exception {
|
|
|
+ try {
|
|
|
+ PublicKey publicKey = loadPublicKey(publicKeyBase64);
|
|
|
+ Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, BC);
|
|
|
+ signature.initVerify(publicKey);
|
|
|
+ signature.update(data);
|
|
|
+ return signature.verify(decodeBase64(signBase64));
|
|
|
+ } catch (Exception ex) {
|
|
|
+ SM2 sm2 = SmUtil.sm2(null, publicKeyBase64.trim());
|
|
|
+ return sm2.verify(data, decodeBase64(signBase64));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 与 SM3Util.encryptByPublicKey 一致:BC Cipher SM2。 */
|
|
|
+ private static byte[] encryptByPublicKey(String data, String publicKeyBase64) throws Exception {
|
|
|
+ Exception last = null;
|
|
|
+ try {
|
|
|
+ return encryptByPublicKeyCipher(data, publicKeyBase64);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ last = ex;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return encryptByPublicKeyEngine(data, publicKeyBase64, SM2Engine.Mode.C1C2C3);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ last = ex;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return encryptByPublicKeyEngine(data, publicKeyBase64, SM2Engine.Mode.C1C3C2);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ last = ex;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ SM2 sm2 = SmUtil.sm2(null, publicKeyBase64.trim());
|
|
|
+ return sm2.encrypt(data.getBytes(StandardCharsets.UTF_8), KeyType.PublicKey);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ last = ex;
|
|
|
+ }
|
|
|
+ throw last != null ? last : new IllegalStateException("SM2 加密失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 与 SM3Util.decryptByPrivateKey 一致,并兼容 C1C3C2 密文。 */
|
|
|
+ private static byte[] decryptByPrivateKey(byte[] cipherBytes, String privateKeyBase64) throws Exception {
|
|
|
+ Exception last = null;
|
|
|
+ try {
|
|
|
+ return decryptByPrivateKeyCipher(cipherBytes, privateKeyBase64);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ last = ex;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return decryptByPrivateKeyEngine(cipherBytes, privateKeyBase64, SM2Engine.Mode.C1C2C3);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ last = ex;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return decryptByPrivateKeyEngine(cipherBytes, privateKeyBase64, SM2Engine.Mode.C1C3C2);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ last = ex;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ SM2 sm2 = SmUtil.sm2(privateKeyBase64.trim(), null);
|
|
|
+ return sm2.decrypt(cipherBytes, KeyType.PrivateKey);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ last = ex;
|
|
|
+ }
|
|
|
+ throw last != null ? last : new IllegalStateException("SM2 解密失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] encryptByPublicKeyCipher(String data, String publicKeyBase64) throws Exception {
|
|
|
+ PublicKey publicKey = loadPublicKey(publicKeyBase64);
|
|
|
+ Cipher cipher = Cipher.getInstance(SM2, BC);
|
|
|
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
|
|
+ return cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] decryptByPrivateKeyCipher(byte[] cipherBytes, String privateKeyBase64) throws Exception {
|
|
|
+ PrivateKey privateKey = loadPrivateKey(privateKeyBase64);
|
|
|
+ Cipher cipher = Cipher.getInstance(SM2, BC);
|
|
|
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
|
|
+ return cipher.doFinal(cipherBytes);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] encryptByPublicKeyEngine(String data, String publicKeyBase64, SM2Engine.Mode mode)
|
|
|
+ throws Exception {
|
|
|
+ PublicKey publicKey = loadPublicKey(publicKeyBase64);
|
|
|
+ ECPublicKeyParameters publicKeyParameters =
|
|
|
+ (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
|
|
|
+ SM2Engine engine = new SM2Engine(mode);
|
|
|
+ engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
|
|
|
+ byte[] input = data.getBytes(StandardCharsets.UTF_8);
|
|
|
+ return engine.processBlock(input, 0, input.length);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] decryptByPrivateKeyEngine(byte[] cipherBytes, String privateKeyBase64, SM2Engine.Mode mode)
|
|
|
+ throws Exception {
|
|
|
+ PrivateKey privateKey = loadPrivateKey(privateKeyBase64);
|
|
|
+ ECPrivateKeyParameters privateKeyParameters =
|
|
|
+ (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
|
|
|
+ SM2Engine engine = new SM2Engine(mode);
|
|
|
+ engine.init(false, privateKeyParameters);
|
|
|
+ return engine.processBlock(cipherBytes, 0, cipherBytes.length);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static PublicKey loadPublicKey(String publicKeyBase64) throws Exception {
|
|
|
+ byte[] keyBytes = decodeBase64(publicKeyBase64);
|
|
|
+ KeyFactory keyFactory = KeyFactory.getInstance(EC, BC);
|
|
|
+ return keyFactory.generatePublic(new X509EncodedKeySpec(keyBytes));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static PrivateKey loadPrivateKey(String privateKeyBase64) throws Exception {
|
|
|
+ byte[] keyBytes = decodeBase64(privateKeyBase64);
|
|
|
+ KeyFactory keyFactory = KeyFactory.getInstance(EC, BC);
|
|
|
+ return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] decodeBase64(String value) {
|
|
|
+ return Base64.getDecoder().decode(value.trim());
|
|
|
+ }
|
|
|
+}
|