diff --git a/lib/bcprov-jdk15to18-1.65.jar b/lib/bcprov-jdk15to18-1.65.jar
new file mode 100644
index 0000000..cbedbc3
Binary files /dev/null and b/lib/bcprov-jdk15to18-1.65.jar differ
diff --git a/pom.xml b/pom.xml
index 3fa172b..67bd6bc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -88,6 +88,13 @@
3.9.0
+
+ org
+ bouncycastle
+ 1.65.8
+ system
+ ${basedir}/lib/bcprov-jdk15to18-1.65.jar
+
diff --git a/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/BcecUtil.java b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/BcecUtil.java
new file mode 100644
index 0000000..aadd0fb
--- /dev/null
+++ b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/BcecUtil.java
@@ -0,0 +1,457 @@
+package com.chinaunicom.mall.ebtp.extend.singlePoint.common;
+
+import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ECPoint;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.*;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemReader;
+import org.bouncycastle.util.io.pem.PemWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.math.BigInteger;
+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.Arrays;
+
+
+/**
+ * 这个工具类的方法,也适用于其他基于BC库的ECC算法
+ *
+ * @author renxx20
+ */
+public final class BcecUtil {
+ private static final Logger logger = LoggerFactory.getLogger(BcecUtil.class);
+
+ private static final String ALGO_NAME_EC = "EC";
+ private static final String PEM_STRING_PUBLIC = "PUBLIC KEY";
+ private static final String PEM_STRING_ECPRIVATEKEY = "EC PRIVATE KEY";
+
+ private BcecUtil() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ /**
+ * 生成ECC密钥对
+ *
+ * @return ECC密钥对
+ */
+ public static AsymmetricCipherKeyPair generateKeyPairParameter(ECDomainParameters domainParameters,
+ SecureRandom random) {
+ ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters,
+ random);
+ ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
+ keyGen.init(keyGenerationParams);
+ return keyGen.generateKeyPair();
+ }
+
+ public static KeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
+ try {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
+ ECParameterSpec parameterSpec = new ECParameterSpec(domainParameters.getCurve(), domainParameters.getG(),
+ domainParameters.getN(), domainParameters.getH());
+ kpg.initialize(parameterSpec, random);
+ return kpg.generateKeyPair();
+ } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
+ logger.error(e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ public static int getCurveLength(ECKeyParameters ecKey) {
+ return getCurveLength(ecKey.getParameters());
+ }
+
+ public static int getCurveLength(ECDomainParameters domainParams) {
+ return (domainParams.getCurve().getFieldSize() + 7) / 8;
+ }
+
+ public static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) {
+ if (src.length == curveLength) {
+ return src;
+ }
+
+ byte[] result = new byte[curveLength];
+ if (src.length > curveLength) {
+ System.arraycopy(src, src.length - result.length, result, 0, result.length);
+ } else {
+ System.arraycopy(src, 0, result, result.length - src.length, src.length);
+ }
+ return result;
+ }
+
+ public static ECPrivateKeyParameters createEcPrivateKeyParameters(BigInteger d,
+ ECDomainParameters domainParameters) {
+ return new ECPrivateKeyParameters(d, domainParameters);
+ }
+
+ public static ECPublicKeyParameters createEcPublicKeyParameters(BigInteger x, BigInteger y,
+ ECCurve curve,
+ ECDomainParameters domainParameters) {
+ return createEcPublicKeyParameters(x.toByteArray(), y.toByteArray(), curve, domainParameters);
+ }
+
+ public static ECPublicKeyParameters createEcPublicKeyParameters(String xHex, String yHex,
+ ECCurve curve,
+ ECDomainParameters domainParameters) {
+ return createEcPublicKeyParameters(ByteUtils.fromHexString(xHex), ByteUtils.fromHexString(yHex),
+ curve, domainParameters);
+ }
+
+ public static ECPublicKeyParameters createEcPublicKeyParameters(byte[] xBytes, byte[] yBytes,
+ ECCurve curve,
+ ECDomainParameters domainParameters) {
+ final byte UNCOMPRESSEDFLAG = 0x04;
+ int curveLength = getCurveLength(domainParameters);
+ xBytes = fixToCurveLengthBytes(curveLength, xBytes);
+ yBytes = fixToCurveLengthBytes(curveLength, yBytes);
+ byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
+ encodedPubKey[0] = UNCOMPRESSEDFLAG;
+ System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
+ System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
+ return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
+ }
+
+ public static ECPrivateKeyParameters convertPrivateKeyToParameters(BCECPrivateKey ecPriKey) {
+ ECParameterSpec parameterSpec = ecPriKey.getParameters();
+ ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
+ parameterSpec.getN(), parameterSpec.getH());
+ return new ECPrivateKeyParameters(ecPriKey.getD(), domainParameters);
+ }
+
+ public static ECPublicKeyParameters convertPublicKeyToParameters(BCECPublicKey ecPubKey) {
+ ECParameterSpec parameterSpec = ecPubKey.getParameters();
+ ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
+ parameterSpec.getN(), parameterSpec.getH());
+ return new ECPublicKeyParameters(ecPubKey.getQ(), domainParameters);
+ }
+
+ public static BCECPublicKey createPublicKeyFromSubjectPublicKeyInfo(SubjectPublicKeyInfo subPubInfo) {
+ try {
+ return BcecUtil.convertX509ToEcPublicKey(subPubInfo.toASN1Primitive().getEncoded(ASN1Encoding.DER));
+ } catch (IOException e) {
+ logger.error(e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * 将ECC私钥转换为PKCS8标准的字节流
+ *
+ * @param priKey
+ * @param pubKey 可以为空,但是如果为空的话得到的结果OpenSSL可能解析不了
+ * @return
+ */
+ public static byte[] convertEcPrivateKeyToPkcs8(ECPrivateKeyParameters priKey,
+ ECPublicKeyParameters pubKey) {
+ ECDomainParameters domainParams = priKey.getParameters();
+ ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
+ domainParams.getN(), domainParams.getH());
+ BCECPublicKey publicKey = null;
+ if (pubKey != null) {
+ publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
+ BouncyCastleProvider.CONFIGURATION);
+ }
+ BCECPrivateKey privateKey = new BCECPrivateKey(ALGO_NAME_EC, priKey, publicKey,
+ spec, BouncyCastleProvider.CONFIGURATION);
+ return privateKey.getEncoded();
+ }
+
+ /**
+ * 将PKCS8标准的私钥字节流转换为私钥对象
+ *
+ * @param pkcs8Key
+ * @return
+ */
+ public static BCECPrivateKey convertPkcs8ToEcPrivateKey(byte[] pkcs8Key) {
+ PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(pkcs8Key);
+ try {
+ KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
+ return (BCECPrivateKey) kf.generatePrivate(peks);
+ } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException e) {
+ logger.error(e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * 将PKCS8标准的私钥字节流转换为PEM
+ *
+ * @param encodedKey
+ * @return
+ * @throws IOException
+ */
+ public static String convertEcPrivateKeyPkcs8ToPem(byte[] encodedKey) throws IOException {
+ return convertEncodedDataToPem(PEM_STRING_ECPRIVATEKEY, encodedKey);
+ }
+
+ /**
+ * 将PEM格式的私钥转换为PKCS8标准字节流
+ *
+ * @param pemString
+ * @return
+ * @throws IOException
+ */
+ public static byte[] convertEcPrivateKeyPemToPkcs8(String pemString) throws IOException {
+ return convertPemToEncodedData(pemString);
+ }
+
+ /**
+ * 将ECC私钥转换为SEC1标准的字节流
+ * openssl d2i_ECPrivateKey函数要求的DER编码的私钥也是SEC1标准的,
+ * 这个工具函数的主要目的就是为了能生成一个openssl可以直接“识别”的ECC私钥.
+ * 相对RSA私钥的PKCS1标准,ECC私钥的标准为SEC1
+ *
+ * @param priKey
+ * @param pubKey
+ * @return
+ * @throws IOException
+ */
+ public static byte[] convertEcPrivateKeyToSec1(ECPrivateKeyParameters priKey,
+ ECPublicKeyParameters pubKey) throws IOException {
+ byte[] pkcs8Bytes = convertEcPrivateKeyToPkcs8(priKey, pubKey);
+ PrivateKeyInfo pki = PrivateKeyInfo.getInstance(pkcs8Bytes);
+ ASN1Encodable encodable = pki.parsePrivateKey();
+ ASN1Primitive primitive = encodable.toASN1Primitive();
+ byte[] sec1Bytes = primitive.getEncoded();
+ logger.info(Arrays.toString(sec1Bytes));
+ return sec1Bytes;
+ }
+
+ /**
+ * 将SEC1标准的私钥字节流恢复为PKCS8标准的字节流
+ *
+ * @param sec1Key
+ * @return
+ * @throws IOException
+ */
+ public static byte[] convertEcPrivateKeySec1ToPkcs8(byte[] sec1Key) throws IOException {
+ /**
+ * 参考org.bouncycastle.asn1.pkcs.PrivateKeyInfo和
+ * org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey,逆向拼装
+ */
+ X962Parameters params = getDomainParametersFromName(Sm2Util.JDK_EC_SPEC, false);
+ ASN1OctetString privKey = new DEROctetString(sec1Key);
+ ASN1EncodableVector v = new ASN1EncodableVector();
+ //版本号
+ v.add(new ASN1Integer(0));
+ //算法标识
+ v.add(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params));
+ v.add(privKey);
+ DERSequence ds = new DERSequence(v);
+ return ds.getEncoded(ASN1Encoding.DER);
+ }
+
+ /**
+ * 将SEC1标准的私钥字节流转为BCECPrivateKey对象
+ *
+ * @param sec1Key
+ * @return
+ */
+ public static BCECPrivateKey convertSec1ToBcecPrivateKey(byte[] sec1Key) {
+ try {
+ PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(convertEcPrivateKeySec1ToPkcs8(sec1Key));
+ KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
+ return (BCECPrivateKey) kf.generatePrivate(peks);
+ } catch (NoSuchAlgorithmException | IOException | NoSuchProviderException | InvalidKeySpecException e) {
+ logger.error(e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * 将SEC1标准的私钥字节流转为ECPrivateKeyParameters对象
+ * openssl i2d_ECPrivateKey函数生成的DER编码的ecc私钥是:SEC1标准的、带有EC_GROUP、带有公钥的,
+ * 这个工具函数的主要目的就是为了使Java程序能够“识别”openssl生成的ECC私钥
+ *
+ * @param sec1Key
+ * @return
+ */
+ public static ECPrivateKeyParameters convertSec1ToEcPrivateKey(byte[] sec1Key) {
+ BCECPrivateKey privateKey = convertSec1ToBcecPrivateKey(sec1Key);
+ if (privateKey == null) {
+ return null;
+ }
+ return convertPrivateKeyToParameters(privateKey);
+ }
+
+ /**
+ * 将ECC公钥对象转换为X509标准的字节流
+ *
+ * @param pubKey
+ * @return
+ */
+ public static byte[] convertEcPublicKeyToX509(ECPublicKeyParameters pubKey) {
+ ECDomainParameters domainParams = pubKey.getParameters();
+ ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
+ domainParams.getN(), domainParams.getH());
+ BCECPublicKey publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
+ BouncyCastleProvider.CONFIGURATION);
+ return publicKey.getEncoded();
+ }
+
+ /**
+ * 将X509标准的公钥字节流转为公钥对象
+ *
+ * @param x509Bytes
+ * @return
+ * @throws NoSuchProviderException
+ * @throws NoSuchAlgorithmException
+ * @throws InvalidKeySpecException
+ */
+ public static BCECPublicKey convertX509ToEcPublicKey(byte[] x509Bytes) {
+ try {
+ X509EncodedKeySpec eks = new X509EncodedKeySpec(x509Bytes);
+ KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
+ return (BCECPublicKey) kf.generatePublic(eks);
+ } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException e) {
+ logger.error(e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * 将X509标准的公钥字节流转为PEM
+ *
+ * @param encodedKey
+ * @return
+ * @throws IOException
+ */
+ public static String convertEcPublicKeyX509ToPem(byte[] encodedKey) throws IOException {
+ return convertEncodedDataToPem(PEM_STRING_PUBLIC, encodedKey);
+ }
+
+ /**
+ * 将PEM格式的公钥转为X509标准的字节流
+ *
+ * @param pemString
+ * @return
+ * @throws IOException
+ */
+ public static byte[] convertEcPublicKeyPemToX509(String pemString) throws IOException {
+ return convertPemToEncodedData(pemString);
+ }
+
+ /**
+ * copy from BC
+ *
+ * @param genSpec
+ * @return
+ */
+ public static X9ECParameters getDomainParametersFromGenSpec(ECGenParameterSpec genSpec) {
+ return getDomainParametersFromName(genSpec.getName());
+ }
+
+ /**
+ * copy from BC
+ *
+ * @param curveName
+ * @return
+ */
+ public static X9ECParameters getDomainParametersFromName(String curveName) {
+ X9ECParameters domainParameters;
+ try {
+ char zero = '0';
+ char two = '2';
+ char nullStr = ' ';
+ if (curveName.charAt(0) >= zero && curveName.charAt(0) <= two) {
+ ASN1ObjectIdentifier oidId = new ASN1ObjectIdentifier(curveName);
+ domainParameters = ECUtil.getNamedCurveByOid(oidId);
+ } else {
+ if (curveName.indexOf(nullStr) > -1) {
+ curveName = curveName.substring(curveName.indexOf(' ') + 1);
+ domainParameters = ECUtil.getNamedCurveByName(curveName);
+ } else {
+ domainParameters = ECUtil.getNamedCurveByName(curveName);
+ }
+ }
+ } catch (IllegalArgumentException ex) {
+ domainParameters = ECUtil.getNamedCurveByName(curveName);
+ }
+ return domainParameters;
+ }
+
+ /**
+ * copy from BC
+ * 405行之后的注释内容
+ * // 如果是1.62或更低版本的bcprov-jdk15on应该使用以下这段代码,
+ * 因为高版本的EC5Util.convertPoint没有向下兼容
+ * X9ECParameters ecP = new X9ECParameters(curve,EC5Util.convertPoint(curve, ecSpec.getGenerator(),
+ * withCompression),ecSpec.getOrder(),BigInteger.valueOf(ecSpec.getCofactor()),ecSpec.getCurve().getSeed())
+ *
+ * @param ecSpec
+ * @param withCompression
+ * @return
+ */
+ public static X962Parameters getDomainParametersFromName(java.security.spec.ECParameterSpec ecSpec,
+ boolean withCompression) {
+ X962Parameters params;
+
+ if (ecSpec instanceof ECNamedCurveSpec) {
+ ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec) ecSpec).getName());
+ if (curveOid == null) {
+ curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec) ecSpec).getName());
+ }
+ params = new X962Parameters(curveOid);
+ } else if (ecSpec == null) {
+ params = new X962Parameters(DERNull.INSTANCE);
+ } else {
+ ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
+
+ X9ECParameters ecP = new X9ECParameters(
+ curve,
+ new X9ECPoint(EC5Util.convertPoint(curve, ecSpec.getGenerator()), withCompression),
+ ecSpec.getOrder(),
+ BigInteger.valueOf(ecSpec.getCofactor()),
+ ecSpec.getCurve().getSeed());
+
+ params = new X962Parameters(ecP);
+ }
+
+ return params;
+ }
+
+ private static String convertEncodedDataToPem(String type, byte[] encodedData) throws IOException {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
+ try {
+ PemObject pemObj = new PemObject(type, encodedData);
+ pWrt.writeObject(pemObj);
+ } finally {
+ pWrt.close();
+ }
+ return new String(bOut.toByteArray());
+ }
+
+ private static byte[] convertPemToEncodedData(String pemString) throws IOException {
+ ByteArrayInputStream bIn = new ByteArrayInputStream(pemString.getBytes());
+ PemReader pRdr = new PemReader(new InputStreamReader(bIn));
+ try {
+ PemObject pemObject = pRdr.readPemObject();
+ return pemObject.getContent();
+ } finally {
+ pRdr.close();
+ }
+ }
+}
diff --git a/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/GmBaseUtil.java b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/GmBaseUtil.java
new file mode 100644
index 0000000..cc80824
--- /dev/null
+++ b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/GmBaseUtil.java
@@ -0,0 +1,23 @@
+package com.chinaunicom.mall.ebtp.extend.singlePoint.common;
+
+import groovy.util.logging.Slf4j;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Security;
+
+/**
+ * @author renxx20
+ */
+@Slf4j
+public class GmBaseUtil {
+ private static final Logger logger = LoggerFactory.getLogger(GmBaseUtil.class);
+
+ protected GmBaseUtil() {
+ logger.info("GmBaseUtil");
+ }
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+}
diff --git a/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/Sm2Cipher.java b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/Sm2Cipher.java
new file mode 100644
index 0000000..1330504
--- /dev/null
+++ b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/Sm2Cipher.java
@@ -0,0 +1,58 @@
+package com.chinaunicom.mall.ebtp.extend.singlePoint.common;
+
+/**
+ * @author renxx20
+ */
+public class Sm2Cipher {
+ /**
+ * ECC密钥
+ */
+ private byte[] c1;
+
+ /**
+ * 真正的密文
+ */
+ private byte[] c2;
+
+ /**
+ * 对(c1+c2)的SM3-HASH值
+ */
+ private byte[] c3;
+
+ /**
+ * SM2标准的密文,即(c1+c2+c3)
+ */
+ private byte[] cipherText;
+
+ public byte[] getC1() {
+ return c1;
+ }
+
+ public void setC1(byte[] c1) {
+ this.c1 = c1;
+ }
+
+ public byte[] getC2() {
+ return c2;
+ }
+
+ public void setC2(byte[] c2) {
+ this.c2 = c2;
+ }
+
+ public byte[] getC3() {
+ return c3;
+ }
+
+ public void setC3(byte[] c3) {
+ this.c3 = c3;
+ }
+
+ public byte[] getCipherText() {
+ return cipherText;
+ }
+
+ public void setCipherText(byte[] cipherText) {
+ this.cipherText = cipherText;
+ }
+}
diff --git a/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/Sm2Encryptor.java b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/Sm2Encryptor.java
new file mode 100644
index 0000000..bff7177
--- /dev/null
+++ b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/Sm2Encryptor.java
@@ -0,0 +1,105 @@
+
+package com.chinaunicom.mall.ebtp.extend.singlePoint.common;
+
+import lombok.extern.slf4j.Slf4j;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 加密/解密工具类
+ *
+ * @author renxx20
+ * @version V1.0
+ * @date 2020-05-10
+ */
+@Slf4j
+public class Sm2Encryptor {
+ private static final Logger logger = LoggerFactory.getLogger(Sm2Encryptor.class);
+ /**
+ * 使用给定的私钥解密给定的字
+ * 前端密文解密 使用
+ *
+ * @param privateKeyStr sessionId
+ * @param encryptText 密文。
+ * @return 原文字符串。
+ */
+ public String decryptString(String privateKeyStr, String encryptText) throws InvalidCipherTextException {
+ ECPrivateKeyParameters priKey =
+ new ECPrivateKeyParameters(new BigInteger(ByteUtils.fromHexString(privateKeyStr)),
+ Sm2Util.DOMAIN_PARAMS);
+ byte[] decryptedData = Sm2Util.decrypt(priKey, fromHexString(encryptText));
+ return new String(decryptedData, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 公钥加密
+ *
+ * @param data 待加密字符串
+ * @param publicKeyStr 公钥
+ * @return 加密后字符串
+ */
+ public String encryptString(String data, String publicKeyStr) throws InvalidCipherTextException {
+ String publicXandY = publicKeyStr.substring(2);
+ int selfLen = publicXandY.length() / 2;
+ int len = publicXandY.length();
+ String xHex = publicXandY.substring(0, selfLen);
+ String yHex = publicXandY.substring(selfLen, len);
+ ECPublicKeyParameters pubKey
+ = BcecUtil.createEcPublicKeyParameters(xHex, yHex, Sm2Util.CURVE, Sm2Util.DOMAIN_PARAMS);
+ byte[] encryptedData = Sm2Util.encrypt(pubKey, data.getBytes());
+ return ByteUtils.toHexString(encryptedData);
+ }
+
+ /**
+ * 生成秘钥, 供工具类调用
+ */
+ public void generateKey() {
+ //生成 SM2密钥对
+ AsymmetricCipherKeyPair keyPair = Sm2Util.generateKeyPairParameter();
+ ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate();
+ ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic();
+
+ String privateKey = ByteUtils.toHexString(priKey.getD().toByteArray()).toUpperCase();
+ String publicKey = ByteUtils.toHexString(pubKey.getQ().getEncoded(false)).toUpperCase();
+ logger.info("SM2公钥,请配置在unifast.security.public-key: {}", publicKey);
+ logger.info("SM2私钥,请配置在unifast.security.private-key: : {}", privateKey);
+ }
+
+ public static void main(String[] args) {
+ Sm2Encryptor encryptor = new Sm2Encryptor();
+ encryptor.generateKey();
+ }
+
+ /**
+ * 将16进制字符串转换为byte[]
+ * 16进制字符串不区分大小写,返回的数组相同
+ *
+ * @param hexString 16进制字符串
+ * @return byte[]
+ */
+ private static byte[] fromHexString(String hexString) {
+ if (null == hexString || "".equals(hexString.trim())) {
+ return new byte[0];
+ }
+ byte[] bytes = new byte[hexString.length() / 2];
+ // 16进制字符串
+ String hex;
+ int two = 2;
+ for (int i = 0; i < hexString.length() / two; i++) {
+ // 每次截取2位
+ hex = hexString.substring(i * 2, i * 2 + 2);
+ // 16进制-->十进制
+ bytes[i] = (byte) Integer.parseInt(hex, 16);
+ }
+ return bytes;
+ }
+}
+
diff --git a/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/Sm2Util.java b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/Sm2Util.java
new file mode 100644
index 0000000..2062d4f
--- /dev/null
+++ b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/Sm2Util.java
@@ -0,0 +1,545 @@
+package com.chinaunicom.mall.ebtp.extend.singlePoint.common;
+
+import org.bouncycastle.asn1.*;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.engines.SM2Engine;
+import org.bouncycastle.crypto.engines.SM2Engine.Mode;
+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.math.ec.ECPoint;
+import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.ECFieldFp;
+import java.security.spec.EllipticCurve;
+
+/**
+ * @author renxx20
+ */
+public class Sm2Util extends GmBaseUtil {
+ //////////////////////////////////////////////////////////////////////////////////////
+ /**
+ * 以下为SM2推荐曲线参数
+ */
+ public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
+ public static final BigInteger SM2_ECC_P = CURVE.getQ();
+ public static final BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
+ public static final BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
+ public static final BigInteger SM2_ECC_N = CURVE.getOrder();
+ public static final BigInteger SM2_ECC_H = CURVE.getCofactor();
+ public static final BigInteger SM2_ECC_GX = new BigInteger(
+ "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
+ public static final BigInteger SM2_ECC_GY = new BigInteger(
+ "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
+ public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
+ public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
+ SM2_ECC_N, SM2_ECC_H);
+ public static final int CURVE_LEN = BcecUtil.getCurveLength(DOMAIN_PARAMS);
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
+ public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
+ G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
+ public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
+ JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());
+
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ public static final int SM3_DIGEST_LENGTH = 32;
+ private static final String UNSUPPORTEDMODE = "Unsupported mode:";
+
+ /**
+ * 生成ECC密钥对
+ *
+ * @return ECC密钥对
+ */
+ public static AsymmetricCipherKeyPair generateKeyPairParameter() {
+ SecureRandom random = new SecureRandom();
+ return BcecUtil.generateKeyPairParameter(DOMAIN_PARAMS, random);
+ }
+
+ public static KeyPair generateKeyPair() {
+ SecureRandom random = new SecureRandom();
+ return BcecUtil.generateKeyPair(DOMAIN_PARAMS, random);
+ }
+
+ /**
+ * 只获取私钥里的d,32字节
+ *
+ * @param privateKey
+ * @return
+ */
+ public static byte[] getRawPrivateKey(BCECPrivateKey privateKey) {
+ return fixToCurveLengthBytes(privateKey.getD().toByteArray());
+ }
+
+ /**
+ * 只获取公钥里的XY分量,64字节
+ *
+ * @param publicKey
+ * @return
+ */
+ public static byte[] getRawPublicKey(BCECPublicKey publicKey) {
+ byte[] src65 = publicKey.getQ().getEncoded(false);
+ //SM2的话这里应该是64字节
+ byte[] rawXandY = new byte[CURVE_LEN * 2];
+ System.arraycopy(src65, 1, rawXandY, 0, rawXandY.length);
+ return rawXandY;
+ }
+
+ /**
+ * @param pubKey
+ * @param srcData
+ * @return 默认输出C1C3C2顺序的密文
+ * @throws InvalidCipherTextException
+ */
+ public static byte[] encrypt(BCECPublicKey pubKey, byte[] srcData) throws InvalidCipherTextException {
+ ECPublicKeyParameters pubKeyParameters = BcecUtil.convertPublicKeyToParameters(pubKey);
+ return encrypt(Mode.C1C3C2, pubKeyParameters, srcData);
+ }
+
+ /**
+ * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
+ * @param pubKey
+ * @param srcData
+ * @return
+ * @throws InvalidCipherTextException
+ */
+ public static byte[] encrypt(Mode mode, BCECPublicKey pubKey, byte[] srcData) throws InvalidCipherTextException {
+ ECPublicKeyParameters pubKeyParameters = BcecUtil.convertPublicKeyToParameters(pubKey);
+ return encrypt(mode, pubKeyParameters, srcData);
+ }
+
+ /**
+ * @param pubKeyParameters
+ * @param srcData
+ * @return 默认输出C1C3C2顺序的密文
+ * @throws InvalidCipherTextException
+ */
+ public static byte[] encrypt(ECPublicKeyParameters pubKeyParameters, byte[] srcData)
+ throws InvalidCipherTextException {
+ return encrypt(Mode.C1C3C2, pubKeyParameters, srcData);
+ }
+
+ /**
+ * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
+ * @param pubKeyParameters
+ * @param srcData
+ * @return
+ * @throws InvalidCipherTextException
+ */
+ public static byte[] encrypt(Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData)
+ throws InvalidCipherTextException {
+ SM2Engine engine = new SM2Engine(mode);
+ ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
+ engine.init(true, pwr);
+ return engine.processBlock(srcData, 0, srcData.length);
+ }
+
+ /**
+ * @param priKey
+ * @param sm2Cipher 默认输入C1C3C2顺序的密文
+ * @return
+ * @throws InvalidCipherTextException
+ */
+ public static byte[] decrypt(BCECPrivateKey priKey, byte[] sm2Cipher) throws InvalidCipherTextException {
+ ECPrivateKeyParameters priKeyParameters = BcecUtil.convertPrivateKeyToParameters(priKey);
+ return decrypt(Mode.C1C3C2, priKeyParameters, sm2Cipher);
+ }
+
+ /**
+ * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
+ * @param priKey
+ * @param sm2Cipher
+ * @return
+ * @throws InvalidCipherTextException
+ */
+ public static byte[] decrypt(Mode mode, BCECPrivateKey priKey, byte[] sm2Cipher) throws InvalidCipherTextException {
+ ECPrivateKeyParameters priKeyParameters = BcecUtil.convertPrivateKeyToParameters(priKey);
+ return decrypt(mode, priKeyParameters, sm2Cipher);
+ }
+
+ /**
+ * @param priKeyParameters
+ * @param sm2Cipher 默认输入C1C3C2顺序的密文
+ * @return
+ * @throws InvalidCipherTextException
+ */
+ public static byte[] decrypt(ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
+ throws InvalidCipherTextException {
+ return decrypt(Mode.C1C3C2, priKeyParameters, sm2Cipher);
+ }
+
+ /**
+ * 前端密文解密
+ *
+ * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
+ * @param priKeyParameters
+ * @param sm2Cipher
+ * @return
+ * @throws InvalidCipherTextException
+ */
+ public static byte[] decrypt(Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
+ throws InvalidCipherTextException {
+ SM2Engine engine = new SM2Engine(mode);
+ engine.init(false, priKeyParameters);
+ return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
+ }
+
+ /**
+ * 分解SM2密文
+ *
+ * @param cipherText 默认输入C1C3C2顺序的密文
+ * @return
+ */
+ public static Sm2Cipher parseSm2Cipher(byte[] cipherText) {
+ int curveLength = BcecUtil.getCurveLength(DOMAIN_PARAMS);
+ return parseSm2Cipher(Mode.C1C3C2, curveLength, SM3_DIGEST_LENGTH, cipherText);
+ }
+
+ /**
+ * 分解SM2密文
+ *
+ * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
+ * @param cipherText
+ * @return
+ */
+ public static Sm2Cipher parseSm2Cipher(Mode mode, byte[] cipherText) {
+ int curveLength = BcecUtil.getCurveLength(DOMAIN_PARAMS);
+ return parseSm2Cipher(mode, curveLength, SM3_DIGEST_LENGTH, cipherText);
+ }
+
+ /**
+ * @param curveLength
+ * @param digestLength
+ * @param cipherText 默认输入C1C3C2顺序的密文
+ * @return
+ */
+ public static Sm2Cipher parseSm2Cipher(int curveLength, int digestLength,
+ byte[] cipherText) {
+ return parseSm2Cipher(Mode.C1C3C2, curveLength, digestLength, cipherText);
+ }
+
+ /**
+ * 分解SM2密文
+ *
+ * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
+ * @param curveLength ECC曲线长度
+ * @param digestLength HASH长度
+ * @param cipherText SM2密文
+ * @return
+ */
+ public static Sm2Cipher parseSm2Cipher(Mode mode, int curveLength, int digestLength,
+ byte[] cipherText) {
+ byte[] c1 = new byte[curveLength * 2 + 1];
+ byte[] c2 = new byte[cipherText.length - c1.length - digestLength];
+ byte[] c3 = new byte[digestLength];
+
+ System.arraycopy(cipherText, 0, c1, 0, c1.length);
+ if (mode == Mode.C1C2C3) {
+ System.arraycopy(cipherText, c1.length, c2, 0, c2.length);
+ System.arraycopy(cipherText, c1.length + c2.length, c3, 0, c3.length);
+ } else if (mode == Mode.C1C3C2) {
+ System.arraycopy(cipherText, c1.length, c3, 0, c3.length);
+ System.arraycopy(cipherText, c1.length + c3.length, c2, 0, c2.length);
+ } else {
+ throw new RuntimeException(UNSUPPORTEDMODE + mode);
+ }
+
+ Sm2Cipher result = new Sm2Cipher();
+ result.setC1(c1);
+ result.setC2(c2);
+ result.setC3(c3);
+ result.setCipherText(cipherText);
+ return result;
+ }
+
+ /**
+ * DER编码
+ *
+ * @param cipher 默认输入C1C3C2顺序的密文
+ * @return 默认输出按C1C3C2编码的结果
+ */
+ public static byte[] encodeSm2CipherToDer(byte[] cipher) throws IOException {
+ int curveLength = BcecUtil.getCurveLength(DOMAIN_PARAMS);
+ return encodeSm2CipherToDer(Mode.C1C3C2, curveLength, SM3_DIGEST_LENGTH, cipher);
+ }
+
+ /**
+ * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
+ * @param cipher
+ * @return 按指定的mode输出相应的编码结果
+ * @throws IOException
+ */
+ public static byte[] encodeSm2CipherToDer(Mode mode, byte[] cipher) throws IOException {
+ int curveLength = BcecUtil.getCurveLength(DOMAIN_PARAMS);
+ return encodeSm2CipherToDer(mode, curveLength, SM3_DIGEST_LENGTH, cipher);
+ }
+
+ /**
+ * @param curveLength
+ * @param digestLength
+ * @param cipher 默认输入C1C3C2顺序的密文
+ * @return 默认输出按C1C3C2编码的结果
+ * @throws IOException
+ */
+ public static byte[] encodeSm2CipherToDer(int curveLength, int digestLength, byte[] cipher) throws IOException {
+ return encodeSm2CipherToDer(Mode.C1C3C2, curveLength, digestLength, cipher);
+ }
+
+ /**
+ * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
+ * @param curveLength
+ * @param digestLength
+ * @param cipher
+ * @return 按指定的mode输出相应的编码结果
+ * @throws Exception
+ */
+ public static byte[] encodeSm2CipherToDer(Mode mode, int curveLength,
+ int digestLength, byte[] cipher) throws IOException {
+
+ byte[] c1x = new byte[curveLength];
+ byte[] c1y = new byte[curveLength];
+ byte[] c2 = new byte[cipher.length - c1x.length - c1y.length - 1 - digestLength];
+ byte[] c3 = new byte[digestLength];
+
+ int startPos = 1;
+ System.arraycopy(cipher, startPos, c1x, 0, c1x.length);
+ startPos += c1x.length;
+ System.arraycopy(cipher, startPos, c1y, 0, c1y.length);
+ startPos += c1y.length;
+ if (mode == Mode.C1C2C3) {
+ System.arraycopy(cipher, startPos, c2, 0, c2.length);
+ startPos += c2.length;
+ System.arraycopy(cipher, startPos, c3, 0, c3.length);
+ } else if (mode == Mode.C1C3C2) {
+ System.arraycopy(cipher, startPos, c3, 0, c3.length);
+ startPos += c3.length;
+ System.arraycopy(cipher, startPos, c2, 0, c2.length);
+ } else {
+ throw new RuntimeException(UNSUPPORTEDMODE + mode);
+ }
+
+ ASN1Encodable[] arr = new ASN1Encodable[4];
+ arr[0] = new ASN1Integer(c1x);
+ arr[1] = new ASN1Integer(c1y);
+ if (mode == Mode.C1C2C3) {
+ arr[2] = new DEROctetString(c2);
+ arr[3] = new DEROctetString(c3);
+ } else if (mode == Mode.C1C3C2) {
+ arr[2] = new DEROctetString(c3);
+ arr[3] = new DEROctetString(c2);
+ }
+ DERSequence ds = new DERSequence(arr);
+ return ds.getEncoded(ASN1Encoding.DER);
+ }
+
+ /**
+ * 解码DER密文
+ *
+ * @param derCipher 默认输入按C1C3C2顺序编码的密文
+ * @return 输出按C1C3C2排列的字节数组
+ */
+ public static byte[] decodeDerSm2Cipher(byte[] derCipher) {
+ return decodeDerSm2Cipher(Mode.C1C3C2, derCipher);
+ }
+
+ /**
+ * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
+ * @param derCipher
+ * @return 按指定的mode输出相应的解码结果
+ * @throws Exception
+ */
+ public static byte[] decodeDerSm2Cipher(Mode mode, byte[] derCipher) {
+ ASN1Sequence as = ASN1Sequence.getInstance(derCipher);
+ byte[] c3;
+ byte[] c2;
+ if (mode == Mode.C1C2C3) {
+ c2 = ((DEROctetString) as.getObjectAt(2)).getOctets();
+ c3 = ((DEROctetString) as.getObjectAt(3)).getOctets();
+ } else if (mode == Mode.C1C3C2) {
+ c3 = ((DEROctetString) as.getObjectAt(2)).getOctets();
+ c2 = ((DEROctetString) as.getObjectAt(3)).getOctets();
+ } else {
+ throw new RuntimeException(UNSUPPORTEDMODE + mode);
+ }
+
+ int pos = 0;
+ byte[] c1x = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray();
+ byte[] c1y = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray();
+ byte[] cipherText = new byte[1 + c1x.length + c1y.length + c2.length + c3.length];
+ final byte UNCOMPRESSEDFLAG = 0x04;
+ cipherText[0] = UNCOMPRESSEDFLAG;
+ pos += 1;
+ System.arraycopy(c1x, 0, cipherText, pos, c1x.length);
+ pos += c1x.length;
+ System.arraycopy(c1y, 0, cipherText, pos, c1y.length);
+ pos += c1y.length;
+ if (mode == Mode.C1C2C3) {
+ System.arraycopy(c2, 0, cipherText, pos, c2.length);
+ pos += c2.length;
+ System.arraycopy(c3, 0, cipherText, pos, c3.length);
+ } else if (mode == Mode.C1C3C2) {
+ System.arraycopy(c3, 0, cipherText, pos, c3.length);
+ pos += c3.length;
+ System.arraycopy(c2, 0, cipherText, pos, c2.length);
+ }
+ return cipherText;
+ }
+
+ public static byte[] sign(BCECPrivateKey priKey, byte[] srcData) throws CryptoException {
+ ECPrivateKeyParameters priKeyParameters = BcecUtil.convertPrivateKeyToParameters(priKey);
+ return sign(priKeyParameters, null, srcData);
+ }
+
+ /**
+ * ECC私钥签名
+ * 不指定withId,则默认withId为字节数组:"1234567812345678".getBytes()
+ *
+ * @param priKeyParameters ECC私钥
+ * @param srcData 源数据
+ * @return 签名
+ * @throws CryptoException
+ */
+ public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] srcData) throws CryptoException {
+ return sign(priKeyParameters, null, srcData);
+ }
+
+ public static byte[] sign(BCECPrivateKey priKey, byte[] withId, byte[] srcData) throws CryptoException {
+ ECPrivateKeyParameters priKeyParameters = BcecUtil.convertPrivateKeyToParameters(priKey);
+ return sign(priKeyParameters, withId, srcData);
+ }
+
+ /**
+ * ECC私钥签名
+ *
+ * @param priKeyParameters ECC私钥
+ * @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
+ * @param srcData 源数据
+ * @return 签名
+ * @throws CryptoException
+ */
+ public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] withId, byte[] srcData)
+ throws CryptoException {
+ SM2Signer signer = new SM2Signer();
+ CipherParameters param = null;
+ ParametersWithRandom pwr = new ParametersWithRandom(priKeyParameters, new SecureRandom());
+ if (withId != null) {
+ param = new ParametersWithID(pwr, withId);
+ } else {
+ param = pwr;
+ }
+ signer.init(true, param);
+ signer.update(srcData, 0, srcData.length);
+ return signer.generateSignature();
+ }
+
+ /**
+ * 将DER编码的SM2签名解析成64字节的纯R+S字节流
+ *
+ * @param derSign
+ * @return
+ */
+ public static byte[] decodeDerSm2Sign(byte[] derSign) {
+ ASN1Sequence as = ASN1Sequence.getInstance(derSign);
+ byte[] rBytes = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray();
+ byte[] sBytes = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray();
+ //由于大数的补0规则,所以可能会出现33个字节的情况,要修正回32个字节
+ rBytes = fixToCurveLengthBytes(rBytes);
+ sBytes = fixToCurveLengthBytes(sBytes);
+ byte[] rawSign = new byte[rBytes.length + sBytes.length];
+ System.arraycopy(rBytes, 0, rawSign, 0, rBytes.length);
+ System.arraycopy(sBytes, 0, rawSign, rBytes.length, sBytes.length);
+ return rawSign;
+ }
+
+ /**
+ * 把64字节的纯R+S字节流转换成DER编码字节流
+ *
+ * @param rawSign
+ * @return
+ * @throws IOException
+ */
+ public static byte[] encodeSm2SignToDer(byte[] rawSign) throws IOException {
+ //要保证大数是正数
+ BigInteger r = new BigInteger(1, extractBytes(rawSign, 0, 32));
+ BigInteger s = new BigInteger(1, extractBytes(rawSign, 32, 32));
+ ASN1EncodableVector v = new ASN1EncodableVector();
+ v.add(new ASN1Integer(r));
+ v.add(new ASN1Integer(s));
+ return new DERSequence(v).getEncoded(ASN1Encoding.DER);
+ }
+
+ public static boolean verify(BCECPublicKey pubKey, byte[] srcData, byte[] sign) {
+ ECPublicKeyParameters pubKeyParameters = BcecUtil.convertPublicKeyToParameters(pubKey);
+ return verify(pubKeyParameters, null, srcData, sign);
+ }
+
+ /**
+ * ECC公钥验签
+ * 不指定withId,则默认withId为字节数组:"1234567812345678".getBytes()
+ *
+ * @param pubKeyParameters ECC公钥
+ * @param srcData 源数据
+ * @param sign 签名
+ * @return 验签成功返回true,失败返回false
+ */
+ public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] srcData, byte[] sign) {
+ return verify(pubKeyParameters, null, srcData, sign);
+ }
+
+ public static boolean verify(BCECPublicKey pubKey, byte[] withId, byte[] srcData, byte[] sign) {
+ ECPublicKeyParameters pubKeyParameters = BcecUtil.convertPublicKeyToParameters(pubKey);
+ return verify(pubKeyParameters, withId, srcData, sign);
+ }
+
+ /**
+ * ECC公钥验签
+ *
+ * @param pubKeyParameters ECC公钥
+ * @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
+ * @param srcData 源数据
+ * @param sign 签名
+ * @return 验签成功返回true,失败返回false
+ */
+ public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] withId, byte[] srcData, byte[] sign) {
+ SM2Signer signer = new SM2Signer();
+ CipherParameters param;
+ if (withId != null) {
+ param = new ParametersWithID(pubKeyParameters, withId);
+ } else {
+ param = pubKeyParameters;
+ }
+ signer.init(false, param);
+ signer.update(srcData, 0, srcData.length);
+ return signer.verifySignature(sign);
+ }
+
+ private static byte[] extractBytes(byte[] src, int offset, int length) {
+ byte[] result = new byte[length];
+ System.arraycopy(src, offset, result, 0, result.length);
+ return result;
+ }
+
+ private static byte[] fixToCurveLengthBytes(byte[] src) {
+ if (src.length == CURVE_LEN) {
+ return src;
+ }
+
+ byte[] result = new byte[CURVE_LEN];
+ if (src.length > CURVE_LEN) {
+ System.arraycopy(src, src.length - result.length, result, 0, result.length);
+ } else {
+ System.arraycopy(src, 0, result, result.length - src.length, src.length);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/UriGeneratorUtil.java b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/UriGeneratorUtil.java
new file mode 100644
index 0000000..fb4d315
--- /dev/null
+++ b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/common/UriGeneratorUtil.java
@@ -0,0 +1,49 @@
+package com.chinaunicom.mall.ebtp.extend.singlePoint.common;
+
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Map;
+
+
+/**
+ * update [序号][日期YYYY-MM-DD] [更改人姓名][变更描述]
+ *
+ * @author wangwenjing
+ * @version v1.0
+ * @date 2021/2/24
+ */
+@Service
+public class UriGeneratorUtil{
+
+ /**
+ * 根据clientId、用户标识、重定向地址和自定义参数,生成加密串
+ *
+ * @param uid
+ * @param params
+ * @return
+ */
+ public static String generateEncodeUrl(String clientId, String uid, String redirectUrl, String publicKey, Map params) {
+ Sm2Encryptor sm2Encryptor = new Sm2Encryptor();
+ LocalDateTime now = LocalDateTime.now();
+ String splitChar = "@@";
+ StringBuilder builder = new StringBuilder(100);
+ builder.append(clientId).append(splitChar);
+ builder.append(uid).append(splitChar);
+ builder.append(redirectUrl).append(splitChar);
+ builder.append(now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
+ if (params != null && !params.isEmpty()) {
+ builder.append(splitChar);
+ for (Map.Entry entry : params.entrySet()) {
+ builder.append(entry.getKey()).append("=").append(entry.getValue());
+ }
+ }
+ try {
+ return sm2Encryptor.encryptString(builder.toString(),publicKey);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+}
diff --git a/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/controller/SinglePointController.java b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/controller/SinglePointController.java
new file mode 100644
index 0000000..8c5e730
--- /dev/null
+++ b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/controller/SinglePointController.java
@@ -0,0 +1,70 @@
+package com.chinaunicom.mall.ebtp.extend.singlePoint.controller;
+
+import com.chinaunicom.mall.ebtp.common.base.entity.BaseResponse;
+import com.chinaunicom.mall.ebtp.common.base.service.impl.BaseCacheUserServiceImpl;
+import com.chinaunicom.mall.ebtp.extend.signature.entity.ExpertSignature;
+import com.chinaunicom.mall.ebtp.extend.signature.service.ExpertSignatureService;
+import com.chinaunicom.mall.ebtp.extend.singlePoint.service.SinglePointService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+@RestController
+@Api(tags = "")
+@RequestMapping("/v1/singlePoint")
+public class SinglePointController {
+
+
+ @Autowired
+ private SinglePointService singlePointService;
+
+ @Autowired
+ private BaseCacheUserServiceImpl userService;
+ /**
+ * 获取单点登录地址
+ *
+ * @param userId
+ * @return
+ */
+ @ApiOperation("获取单点登录地址")
+ @GetMapping("/getSsoUrl")
+ public BaseResponse getSsoUrl(@ApiParam(value = "用户id", required = false) @RequestParam(name = "userId", required = false) String userId) {
+
+ String uid = userId;
+ if(userService.getCacheUser()!=null&&
+ userService.getCacheUser().getUserId()!=null){
+ uid = userService.getCacheUser().getUserId();
+ }
+ String encodeUrl = singlePointService.generateEncodeUrl(userId);
+
+ return BaseResponse.success(encodeUrl);
+ }
+
+ /**
+ * 获取单点登录地址 + 指定公告参数
+ *
+ * @param userId
+ * @return
+ */
+ @ApiOperation("获取单点登录地址 + 指定参数")
+ @GetMapping("/getSsoUrlByPage")
+ public BaseResponse getSsoUrlByPage(@RequestParam(name = "userId", required = false) String userId,
+ @RequestParam(name = "page", required = false) String page) {
+
+ String uid = userId;
+ if(userService.getCacheUser()!=null&&
+ userService.getCacheUser().getUserId()!=null){
+ uid = userService.getCacheUser().getUserId();
+ }
+ String encodeUrl = singlePointService.generateEncodeUrl(uid,page);
+
+ return BaseResponse.success(encodeUrl);
+ }
+}
diff --git a/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/service/SinglePointService.java b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/service/SinglePointService.java
new file mode 100644
index 0000000..da13844
--- /dev/null
+++ b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/service/SinglePointService.java
@@ -0,0 +1,22 @@
+package com.chinaunicom.mall.ebtp.extend.singlePoint.service;
+
+import com.chinaunicom.mall.ebtp.extend.signature.entity.ExpertSignature;
+
+public interface SinglePointService {
+
+
+ /**
+ * 根据clientId、用户标识、重定向地址生成加密串
+ *
+ * @param uid
+ * @return
+ */
+ String generateEncodeUrl(String uid);
+ /**
+ * 根据clientId、用户标识、重定向地址生成加密串
+ *
+ * @param uid
+ * @return
+ */
+ String generateEncodeUrl(String uid, String page);
+}
diff --git a/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/service/impl/SinglePointServiceImpl.java b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/service/impl/SinglePointServiceImpl.java
new file mode 100644
index 0000000..57aaf3e
--- /dev/null
+++ b/src/main/java/com/chinaunicom/mall/ebtp/extend/singlePoint/service/impl/SinglePointServiceImpl.java
@@ -0,0 +1,89 @@
+package com.chinaunicom.mall.ebtp.extend.singlePoint.service.impl;
+
+
+import com.chinaunicom.mall.ebtp.extend.singlePoint.common.Sm2Encryptor;
+import com.chinaunicom.mall.ebtp.extend.singlePoint.service.SinglePointService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class SinglePointServiceImpl implements SinglePointService {
+
+ @Value("${unifast.sso.public-key}")
+ private String publicKey;
+ //"04819CF427F9150FEEBD91E8D2346F203FC47312D212022A967D8372EA30B9581CCEEFCE2670BDDAF2E8DA1620EA73948126078ED9FF9773AA3A94EE6C80035A18"
+
+ @Value("${unifast.sso.clientId}")
+ private String clientId;
+
+ @Value("${unifast.sso.redirectUrl}")
+ private String redirectUrl;
+
+ @Value("${unifast.sso.getCode.url}")
+ private String codeUrl;
+
+ /**
+ * 根据clientId、用户标识、重定向地址生成加密串
+ *
+ * @param uid
+ * @return
+ */
+ @Override
+ public String generateEncodeUrl(String uid) {
+ return generateEncodeUrl(uid, null);
+ }
+
+ /**
+ * 根据clientId、用户标识、重定向地址生成加密串
+ *
+ * @param uid
+ * @return
+ */
+ @Override
+ public String generateEncodeUrl(String uid,String page) {
+ return generateEncodeUrl(clientId, uid, redirectUrl,publicKey,page,null);
+ }
+
+
+ /**
+ * 根据clientId、用户标识、重定向地址和自定义参数,生成加密串
+ *
+ * @param uid
+ * @param params
+ * @return
+ */
+ private String generateEncodeUrl(String clientId, String uid, String redirectUrl,String publicKey,String page, Map params) {
+ Sm2Encryptor sm2Encryptor = new Sm2Encryptor();
+ LocalDateTime now = LocalDateTime.now();
+ String splitChar = "@@";
+ StringBuilder builder = new StringBuilder(100);
+ builder.append(clientId).append(splitChar);
+ builder.append(uid).append(splitChar);
+ String pageValue = "";
+ if(page!=null&&!"".equals(page)){
+ pageValue = "?page="+page;
+ }
+ builder.append(redirectUrl+pageValue).append(splitChar);
+ builder.append(now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
+ if (params != null && !params.isEmpty()) {
+ builder.append(splitChar);
+ for (Map.Entry entry : params.entrySet()) {
+ builder.append(entry.getKey()).append("=").append(entry.getValue());
+ builder.append("&");
+ }
+ builder.append("1=1");
+ }
+ try {
+ return codeUrl+"?"+ sm2Encryptor.encryptString(builder.toString(),publicKey);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+}