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 ""; + } +}