diff --git a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/demo/DemoController.java b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/demo/DemoController.java index 5dd4a2f..49abd19 100644 --- a/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/demo/DemoController.java +++ b/server/zyjblogs-oauth/src/main/java/cn/zyjblogs/server/demo/DemoController.java @@ -1,6 +1,5 @@ package cn.zyjblogs.server.demo; -import cn.zyjblogs.starter.common.utils.crypto.Sm2Utils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -20,20 +19,4 @@ public class DemoController { public String success() { return "登录成功"; } - - public static void main(String[] args) { - String data = "9b758fceca85fb0ddbaaf649ad9f877f2ac8d0c5752228833aa2b58a5f6ecfab0d074cf1e0fdae5ab63b61b8a67182fb547f64ba76684a71240cb8c7a025efa230880fea071315048a0563bd27109c5a2c76b0b56b05424eb9555f7d13142437260f3bbf61152ab31078b6965a11705a8ec807d257c0fecd906d7a2f87e8f1cb717a"; - String pub = "0417f347d7fa08ae6ad9bf8ef6ac6c313810e05044290f7c18dc9b913b252603505cf7cdbf7ac7d88de508e78bbc2d74cb28c0a90724ed4b751cc69bdfe55b68de"; - String pri = "73d76cf4f553535d6ec45478fb1581baa0c83e166b347af10ab129966d3f187f"; - try { - String decrypt = Sm2Utils.encrypt(pri, data, true); - System.out.println(decrypt); - String encrypt = Sm2Utils.encrypt(pub, decrypt, true); - System.out.println(encrypt); - String decrypt1 = Sm2Utils.decrypt(pri, encrypt, true); - System.out.println(decrypt1); - } catch (Exception e) { - throw new RuntimeException(e); - } - } } diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/Sm2Utils.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/Sm2Utils.java index 2595658..6c6f2ae 100644 --- a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/Sm2Utils.java +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/Sm2Utils.java @@ -5,16 +5,6 @@ package cn.zyjblogs.starter.common.utils.crypto; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.SecureRandom; -import java.security.Security; -import java.security.Signature; -import java.security.spec.ECGenParameterSpec; -import java.util.Base64; import org.bouncycastle.asn1.gm.GMNamedCurves; import org.bouncycastle.asn1.gm.GMObjectIdentifiers; import org.bouncycastle.asn1.x9.X9ECParameters; @@ -33,6 +23,12 @@ import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.encoders.Hex; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.ECGenParameterSpec; +import java.util.Base64; + public class Sm2Utils { private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1"); private static byte SM2_CIPHER_FIRST_BIT = 4; @@ -42,16 +38,15 @@ public class Sm2Utils { } public static String encrypt(String publicKey, String data) throws Exception { - return encrypt(publicKey, data, true); + return encrypt(publicKey, data, Mode.C1C3C2); } - public static String encrypt(String publicKey, String data, boolean isNew) throws Exception { + public static String encrypt(String publicKey, String data, SM2Engine.Mode mode) throws Exception { BCECPublicKey bcecPublicKey = getPublicKey(publicKey); ECParameterSpec parameters = bcecPublicKey.getParameters(); ECDomainParameters ecDomainParameters = new ECDomainParameters(parameters.getCurve(), parameters.getG(), parameters.getN()); ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters); - SM2Engine.Mode model = isNew ? Mode.C1C3C2 : Mode.C1C2C3; - SM2Engine sm2Engine = new SM2Engine(model); + SM2Engine sm2Engine = new SM2Engine(mode); sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom())); byte[] in = data.getBytes(StandardCharsets.UTF_8); byte[] bytes = sm2Engine.processBlock(in, 0, in.length); @@ -60,18 +55,17 @@ public class Sm2Utils { } public static String decrypt(String privateKey, String data) throws Exception { - return decrypt(privateKey, data, true); + return decrypt(privateKey, data, Mode.C1C3C2); } - public static String decrypt(String privateKey, String data, boolean isNew) throws Exception { + public static String decrypt(String privateKey, String data, SM2Engine.Mode mode) throws Exception { byte[] base64Decode = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8)); byte[] finalByte = addBitIfNeed(base64Decode); BCECPrivateKey bcecPrivateKey = getPrivateKey(privateKey); ECParameterSpec parameters = bcecPrivateKey.getParameters(); ECDomainParameters ecDomainParameters = new ECDomainParameters(parameters.getCurve(), parameters.getG(), parameters.getN()); ECPrivateKeyParameters ecPublicKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(), ecDomainParameters); - SM2Engine.Mode model = isNew ? Mode.C1C3C2 : Mode.C1C2C3; - SM2Engine sm2Engine = new SM2Engine(model); + SM2Engine sm2Engine = new SM2Engine(mode); sm2Engine.init(false, ecPublicKeyParameters); byte[] bytes = sm2Engine.processBlock(finalByte, 0, finalByte.length); return new String(bytes, StandardCharsets.UTF_8); @@ -100,10 +94,10 @@ public class Sm2Utils { KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider()); kpg.initialize(sm2Spec, new SecureRandom()); KeyPair keyPair = kpg.generateKeyPair(); - BCECPublicKey bcecPublicKey = (BCECPublicKey)keyPair.getPublic(); + BCECPublicKey bcecPublicKey = (BCECPublicKey) keyPair.getPublic(); String publicKey = Hex.toHexString(bcecPublicKey.getQ().getEncoded(false)); System.out.println("公钥:\n" + publicKey); - BCECPrivateKey bcecPrivateKey = (BCECPrivateKey)keyPair.getPrivate(); + BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) keyPair.getPrivate(); String privateKey = bcecPrivateKey.getD().toString(16); System.out.println("私钥:\n" + privateKey); } @@ -111,13 +105,13 @@ public class Sm2Utils { private static BCECPublicKey getPublicKey(String publicKey) throws Exception { KeyFactory keyFactory = KeyFactory.getInstance("EC"); ECPoint ecPoint = param.getCurve().decodePoint(Hex.decode(publicKey)); - return (BCECPublicKey)keyFactory.generatePublic(new ECPublicKeySpec(ecPoint, param)); + return (BCECPublicKey) keyFactory.generatePublic(new ECPublicKeySpec(ecPoint, param)); } private static BCECPrivateKey getPrivateKey(String privateKey) throws Exception { BigInteger d = new BigInteger(privateKey, 16); KeyFactory keyFactory = KeyFactory.getInstance("EC"); - return (BCECPrivateKey)keyFactory.generatePrivate(new ECPrivateKeySpec(d, param)); + return (BCECPrivateKey) keyFactory.generatePrivate(new ECPrivateKeySpec(d, param)); } private static byte[] addBitIfNeed(byte[] base64Decode) { diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/GmSmException.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/GmSmException.java new file mode 100644 index 0000000..857aa2c --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/GmSmException.java @@ -0,0 +1,22 @@ +package cn.zyjblogs.starter.common.utils.crypto.sm; + +public class GmSmException extends RuntimeException { + public GmSmException() { + } + + public GmSmException(String message) { + super(message); + } + + public GmSmException(String message, Throwable cause) { + super(message, cause); + } + + public GmSmException(Throwable cause) { + super(cause); + } + + public GmSmException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm2/SM2.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm2/SM2.java new file mode 100644 index 0000000..2a3be2e --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm2/SM2.java @@ -0,0 +1,389 @@ +package cn.zyjblogs.starter.common.utils.crypto.sm.sm2; + +import cn.zyjblogs.starter.common.utils.crypto.Sm2Utils; +import cn.zyjblogs.starter.common.utils.crypto.sm.GmSmException; +import org.bouncycastle.asn1.gm.GMNamedCurves; +import org.bouncycastle.asn1.gm.GMObjectIdentifiers; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.engines.SM2Engine; +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.x509.CertificateFactory; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPrivateKeySpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.encoders.Hex; + +import java.io.ByteArrayInputStream; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.Locale; + +/** + * 国密SM2非对称加密算法 + */ +public class SM2 { + + public static final String CRYPTO_NAME_SM2 = "sm2p256v1"; + private static byte SM2_CIPHER_FIRST_BIT = 4; + + public enum EncodeType { + UTF8, + HEX, + BASE64 + } + + + /** + * 生成SM2公私钥对 + *
+ * BC库使用的公钥=64个字节+1个字节(04标志位),BC库使用的私钥=32个字节 + * SM2秘钥的组成部分有 私钥D,公钥X,公钥Y, 他们都可以用长度为64的16进制的HEX串表示, + * SM2公钥并不是直接由X+Y表示, 而是额外添加了一个头,当启用压缩时:公钥=有头+公钥X,即省略了公钥Y的部分 + * + * @param compressed 是否压缩公钥(加密解密都使用BC库才能使用压缩) + * @return SM2 HEX字符串格式秘钥对 + */ + public static SM2KeyPair generateSm2Keys(boolean compressed) { + + // 获取一条SM2曲线参数 + X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2); + // 构造domain参数 + ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN()); + + // 创建秘钥对生成器 + ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); + // 初始化生成器,带上随机数 + keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, new SecureRandom())); + // 生成秘钥对 + AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair(); + + // 把公钥转换为椭圆点 + ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic(); + ECPoint ecPoint = publicKeyParameters.getQ(); + + // 把公钥转换为HEX + // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04,默认压缩公钥 + String publicKey = Hex.toHexString(ecPoint.getEncoded(compressed)).toUpperCase(Locale.ROOT); + + // 把私钥转换为HEX + ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate(); + BigInteger intPrivateKey = privateKeyParameters.getD(); + String privateKey = intPrivateKey.toString(16).toUpperCase(Locale.ROOT); + + // 构造HEX秘钥对,并返回 + return new SM2KeyPair(publicKey, privateKey); + } + + /** + * SM2加密算法 + * + * @param pubKey 公钥 + * @param data 待加密的数据 + * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04 + */ + public static String encrypt(String pubKey, String data) { + + // 按国密排序标准加密 + return encrypt(pubKey, data, SM2EngineExtend.CIPHER_MODE_NORM, EncodeType.UTF8, EncodeType.HEX); + } + + /** + * SM2加密算法 + * + * @param pubKey 公钥 + * @param data 待加密的数据 + * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2; + * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04 + */ + public static String encrypt(String pubKey, String data, int cipherMode, EncodeType inputType, EncodeType outType) { + try { + // 非压缩模式公钥对接放是128位HEX秘钥,需要为BC库加上“04”标记 + if (pubKey.length() == 128) { + pubKey = "04" + pubKey; + } + // 获取一条SM2曲线参数 + X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2); + // 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N + ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN()); + //提取公钥点 + ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(pubKey)); + // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04 + ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters); + + SM2EngineExtend sm2Engine = new SM2EngineExtend(); + // 设置sm2为加密模式 + sm2Engine.init(true, cipherMode, new ParametersWithRandom(publicKeyParameters, new SecureRandom())); + byte[] in; + if (EncodeType.HEX.equals(inputType)) { + in = Hex.decode(data); + } else if (EncodeType.BASE64.equals(inputType)) { + in = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8)); + } else { + in = data.getBytes(StandardCharsets.UTF_8); + } + byte[] arrayOfBytes = sm2Engine.processBlock(in, 0, in.length); + if (EncodeType.BASE64.equals(outType)) { + byte[] base64Bytes = Base64.getEncoder().encode(arrayOfBytes); + return new String(base64Bytes, StandardCharsets.UTF_8); + } else if (EncodeType.HEX.equals(outType)) { + return Hex.toHexString(arrayOfBytes).toUpperCase(Locale.ROOT); + } else { + return new String(arrayOfBytes, StandardCharsets.UTF_8); + } + } catch (Exception e) { + throw new GmSmException(e); + } + + } + + private static byte[] addBitIfNeed(byte[] base64Decode) { + byte first = base64Decode[0]; + if (first == SM2_CIPHER_FIRST_BIT) { + return base64Decode; + } else { + byte[] finalByte = new byte[base64Decode.length + 1]; + finalByte[0] = SM2_CIPHER_FIRST_BIT; + System.arraycopy(base64Decode, 0, finalByte, 1, base64Decode.length); + return finalByte; + } + } + + /** + * SM2解密算法 + * + * @param priKey 私钥 + * @param cipherData 密文数据 + * @return 解密后的数据 + */ + public static String decrypt(String priKey, String cipherData) { + // // 按国密排序标准解密 + return decrypt(priKey, cipherData, SM2EngineExtend.CIPHER_MODE_NORM, EncodeType.HEX, EncodeType.UTF8); + } + + /** + * SM2解密算法 + * + * @param priKey 私钥 + * @param cipherData 密文数据 + * @param cipherMode 密文排列方式 0-C1C2C3;1-C1C3C2; + * @return 解密后的数据 + */ + public static String decrypt(String priKey, String cipherData, int cipherMode, EncodeType inputType, EncodeType outType) { + + try { + byte[] cipherDataByte; + if (EncodeType.HEX.equals(inputType)) { + // 使用BC库加解密时密文以04开头,传入的密文前面没有04则补上 + if (!cipherData.startsWith("04")) { + cipherData = "04" + cipherData; + } + cipherDataByte = Hex.decode(cipherData); + } else if (EncodeType.BASE64.equals(inputType)) { + cipherDataByte = Base64.getDecoder().decode(cipherData); + cipherDataByte = addBitIfNeed(cipherDataByte); + } else { + cipherDataByte = cipherData.getBytes(StandardCharsets.UTF_8); + } + //获取一条SM2曲线参数 + X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2); + //构造domain参数 + ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN()); + + BigInteger privateKeyD = new BigInteger(priKey, 16); + ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters); + + SM2EngineExtend sm2Engine = new SM2EngineExtend(); + // 设置sm2为解密模式 + sm2Engine.init(false, cipherMode, privateKeyParameters); + + byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length); + if (EncodeType.HEX.equals(outType)) { + return Hex.toHexString(arrayOfBytes).toUpperCase(Locale.ROOT); + } else if (EncodeType.BASE64.equals(outType)) { + byte[] base64Bytes = Base64.getEncoder().encode(arrayOfBytes); + return new String(base64Bytes, StandardCharsets.UTF_8); + } else { + return new String(arrayOfBytes, StandardCharsets.UTF_8); + } + } catch (Exception e) { + throw new GmSmException(e); + } + + } + + /** + * 签名 + * + * @param priKey 私钥 + * @param plainText 待签名文本 + * @return 签名 + */ + public static String sign(String priKey, String plainText) { + + try { + // 构造提供器 + BouncyCastleProvider provider = new BouncyCastleProvider(); + // 获取一条SM2曲线参数 + X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2); + // 构造椭圆参数规格 + ECParameterSpec ecParameterSpec = new ECParameterSpec(sm2ECParameters.getCurve(), + sm2ECParameters.getG(), sm2ECParameters.getN(), sm2ECParameters.getH()); + // 创建Key工厂 + KeyFactory keyFactory = KeyFactory.getInstance("EC", provider); + + // 创建签名对象 + Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider); + + // 将私钥HEX字符串转换为X值 + BigInteger bigInteger = new BigInteger(priKey, 16); + // 生成SM2私钥 + BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) keyFactory.generatePrivate(new ECPrivateKeySpec(bigInteger, + ecParameterSpec)); + + // 初始化为签名状态 + signature.initSign(bcecPrivateKey); + // 传入签名字节 + signature.update(plainText.getBytes()); + + // 签名 + return Hex.toHexString(signature.sign()).toUpperCase(Locale.ROOT); + } catch (Exception e) { + throw new GmSmException(e); + } + + } + + /** + * 验签 + * + * @param pubKey 公钥 + * @param plainText 明文 + * @param signatureValue 签名 + * @return 验签结果 + */ + public static boolean verify(String pubKey, String plainText, String signatureValue) { + + // 非压缩模式公钥对接放是128位HEX秘钥,需要为BC库加上“04”标记 + if (pubKey.length() == 128) { + pubKey = "04" + pubKey; + } + + try { + // 构造提供器 + BouncyCastleProvider provider = new BouncyCastleProvider(); + + // 获取一条SM2曲线参数 + X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2); + // 构造椭圆参数规格 + ECParameterSpec ecParameterSpec = new ECParameterSpec(sm2ECParameters.getCurve(), + sm2ECParameters.getG(), sm2ECParameters.getN(), sm2ECParameters.getH()); + // 创建Key工厂 + KeyFactory keyFactory = KeyFactory.getInstance("EC", provider); + + // 创建签名对象 + Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider); + + // 将公钥HEX字符串转换为椭圆曲线对应的点 + ECPoint ecPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(pubKey)); + BCECPublicKey bcecPublicKey = (BCECPublicKey) keyFactory.generatePublic(new ECPublicKeySpec(ecPoint, ecParameterSpec)); + + // 初始化为验签状态 + signature.initVerify(bcecPublicKey); + signature.update(plainText.getBytes()); + + return signature.verify(Hex.decode(signatureValue)); + } catch (Exception e) { + throw new GmSmException(e); + } + + } + + /** + * 证书验签 + * + * @param certStr 证书串 + * @param plaintext 签名原文 + * @param signValueStr 签名产生签名值 此处的签名值实际上就是 R和S的sequence + * @return 证书验证结果 + */ + public static boolean certVerify(String certStr, String plaintext, String signValueStr) { + + try { + // 构造提供器 + BouncyCastleProvider provider = new BouncyCastleProvider(); + + // 解析证书 + byte[] signValue = Hex.decode(signValueStr); + CertificateFactory factory = new CertificateFactory(); + X509Certificate certificate = (X509Certificate) factory + .engineGenerateCertificate(new ByteArrayInputStream(Hex.decode(certStr))); + + // 验证签名 + Signature signature = Signature.getInstance(certificate.getSigAlgName(), provider); + signature.initVerify(certificate); + signature.update(plaintext.getBytes()); + + return signature.verify(signValue); + } catch (Exception e) { + throw new GmSmException(e); + } + + } + + public static void main(String[] args) { + String data = "dPhq2XdoMcgD5m7M0I51SX7MkzMerWMcPdBdv/tX8B5jOyM28n+CcXUn721/9N0ELVgC2P0eBRn4jD04rPScJd5izcC7+xXT5LUwbV2S6wc0g2RC8nkuZITc4rdrACPvNxd18b6y"; + String pub = "0417f347d7fa08ae6ad9bf8ef6ac6c313810e05044290f7c18dc9b913b252603505cf7cdbf7ac7d88de508e78bbc2d74cb28c0a90724ed4b751cc69bdfe55b68de"; + String pri = "73d76cf4f553535d6ec45478fb1581baa0c83e166b347af10ab129966d3f187f"; + String key = "0123456789abcdeffedcba9876543210"; + try { + String decrypt = Sm2Utils.decrypt(pri, data, SM2Engine.Mode.C1C3C2); + System.out.println(decrypt); + String encrypt = Sm2Utils.encrypt(pub, decrypt, SM2Engine.Mode.C1C3C2); + System.out.println(encrypt); + String decrypt1 = Sm2Utils.decrypt(pri, encrypt, SM2Engine.Mode.C1C3C2); + System.out.println(decrypt1); + System.out.println(Sm2Utils.decrypt(pri, "BCWhJJ0BFPt/RuhS37sk22/5GuemkzG7kt+CLwRSz34taiKPjc0TDoY959dCf7C2cZJ2uzLoqRmcH/pV7uWGhPzTIZmKM8wPpVIeuN616dNVm+5/YpaQfcawis6KpJOeeU4fcyrYf9wcawtkow==")); + System.out.println("MDRiZGVjZDU2ODJmNTk5NjJiOTUyOTYwOTQ1MjVjMzcwN2U5MDUxZDJiMDczN2E3MDUxYzNjNDNmMWQyOTczNzBiMTM5YzU3YTA1OGEzMTU3NjM1NGY1NGZjNWE5N2U4Mzg3ZjE1NGM0ZWNhNjIyNDkzMGNlMGIzN2M2ZDI3NjhkMDViMjc4ODY0ODBiN2ZjYzA3YmNkMzM2OWJmNjQ2MGJlN2U0MzQyNTU2NGVkMDQyNzViZDRmZWFmYTUzYzRmOTIwY2Q0OTAxN2ZhMDA=".length()); + System.out.println("04bdecd5682f59962b95296094525c3707e9051d2b0737a7051c3c43f1d297370b139c57a058a31576354f54fc5a97e8387f154c4eca6224930ce0b37c6d2768d05b27886480b7fcc07bcd3369bf6460be7e43425564ed04275bd4feafa53c4f920cd49017fa00".length()); + String decrypt2 = SM2.decrypt(pri, "BCWhJJ0BFPt/RuhS37sk22/5GuemkzG7kt+CLwRSz34taiKPjc0TDoY959dCf7C2cZJ2uzLoqRmcH/pV7uWGhPzTIZmKM8wPpVIeuN616dNVm+5/YpaQfcawis6KpJOeeU4fcyrYf9wcawtkow==", 1, SM2.EncodeType.BASE64, SM2.EncodeType.UTF8); + System.out.println("-------------"); + System.out.println(decrypt2); + String encrypt1 = SM2.encrypt(pub, decrypt2); + System.out.println("-------------"); + System.out.println(encrypt1); + String decrypt3 = SM2.decrypt(pri, encrypt1); + System.out.println("aaa:" + decrypt3); + String decrypt4 = SM2.encrypt(pub, decrypt3, 1, SM2.EncodeType.UTF8, SM2.EncodeType.BASE64); + System.out.println(decrypt4); + System.out.println("-----------"); + String decrypt5 = SM2.decrypt(pri, decrypt4, 1, SM2.EncodeType.BASE64, SM2.EncodeType.BASE64); + System.out.println(decrypt5); + String decrypt5_1 = SM2.encrypt(pub, decrypt5, 1, SM2.EncodeType.BASE64, SM2.EncodeType.BASE64); + System.out.println(decrypt5_1); + String decrypt5_2 = SM2.decrypt(pri, decrypt5_1, 1, SM2.EncodeType.BASE64, SM2.EncodeType.BASE64); + System.out.println(decrypt5_2); + System.out.println(new String(Base64.getDecoder().decode(decrypt5))); + String decrypt6 = SM2.encrypt(pub, decrypt5, 1, SM2.EncodeType.BASE64, SM2.EncodeType.HEX); + System.out.println(decrypt6); + System.out.println("-----------"); + String decrypt7 = SM2.decrypt(pri, decrypt6, 1, SM2.EncodeType.HEX, SM2.EncodeType.UTF8); + System.out.println(decrypt7); + String decrypt8 = SM2.decrypt(pri, data, 1, EncodeType.BASE64, SM2.EncodeType.UTF8); + System.out.println(decrypt8); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm2/SM2EngineExtend.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm2/SM2EngineExtend.java new file mode 100644 index 0000000..efda6b0 --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm2/SM2EngineExtend.java @@ -0,0 +1,293 @@ +package cn.zyjblogs.starter.common.utils.crypto.sm.sm2; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.crypto.params.*; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECFieldElement; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +public class SM2EngineExtend { + private final Digest digest; + + /** + * 是否为加密模式 + */ + private boolean forEncryption; + private ECKeyParameters ecKey; + private ECDomainParameters ecParams; + private int curveLength; + private SecureRandom random; + /** + * 密文排序方式 + */ + private int cipherMode; + + /** + * BC库默认排序方式-C1C2C3 + */ + public static int CIPHER_MODE_BC = 0; + /** + * 国密标准排序方式-C1C3C2 + */ + public static int CIPHER_MODE_NORM = 1; + + public SM2EngineExtend() { + this(new SM3Digest()); + } + + public SM2EngineExtend(Digest digest) { + this.digest = digest; + } + + /** + * 设置密文排序方式 + * + * @param cipherMode 排序方式 + */ + public void setCipherMode(int cipherMode) { + this.cipherMode = cipherMode; + } + + /** + * 默认初始化方法,使用国密排序标准 + * + * @param forEncryption - 是否以加密模式初始化 + * @param param - 曲线参数 + */ + public void init(boolean forEncryption, CipherParameters param) { + init(forEncryption, CIPHER_MODE_NORM, param); + } + + /** + * 默认初始化方法,使用国密排序标准 + * + * @param forEncryption 是否以加密模式初始化 + * @param cipherMode 加密数据排列模式:1-标准排序;0-BC默认排序 + * @param param 曲线参数 + */ + public void init(boolean forEncryption, int cipherMode, CipherParameters param) { + this.forEncryption = forEncryption; + this.cipherMode = cipherMode; + if (forEncryption) { + ParametersWithRandom rParam = (ParametersWithRandom) param; + + ecKey = (ECKeyParameters) rParam.getParameters(); + ecParams = ecKey.getParameters(); + + ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH()); + if (s.isInfinity()) { + throw new IllegalArgumentException("invalid key: [h]Q at infinity"); + } + + random = rParam.getRandom(); + } else { + ecKey = (ECKeyParameters) param; + ecParams = ecKey.getParameters(); + } + + curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8; + } + + /** + * 加密或解密输入数据 + * + * @param in 输入数据字节 + * @param inOff 偏移 + * @param inLen 长度 + * @return 解析后字节 + * @throws InvalidCipherTextException 异常 + */ + public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException { + if (forEncryption) { + // 加密 + return encrypt(in, inOff, inLen); + } else { + return decrypt(in, inOff, inLen); + } + } + + /** + * 加密实现,根据cipherMode输出指定排列的结果,默认按标准方式排列 + * + * @param in 输入数据字节 + * @param inOff 偏移 + * @param inLen 长度 + * @return 解析后字节 + */ + private byte[] encrypt(byte[] in, int inOff, int inLen) { + byte[] c2 = new byte[inLen]; + + System.arraycopy(in, inOff, c2, 0, c2.length); + + byte[] c1; + ECPoint kPB; + do { + BigInteger k = nextK(); + + ECPoint c1P = ecParams.getG().multiply(k).normalize(); + + c1 = c1P.getEncoded(false); + + kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize(); + + kdf(digest, kPB, c2); + } + while (notEncrypted(c2, in, inOff)); + + byte[] c3 = new byte[digest.getDigestSize()]; + + addFieldElement(digest, kPB.getAffineXCoord()); + digest.update(in, inOff, inLen); + addFieldElement(digest, kPB.getAffineYCoord()); + + digest.doFinal(c3, 0); + if (cipherMode == CIPHER_MODE_NORM) { + return Arrays.concatenate(c1, c3, c2); + } + return Arrays.concatenate(c1, c2, c3); + } + + /** + * 解密实现,默认按标准排列方式解密,解密时解出c2部分原文并校验c3部分 + * + * @param in 输入数据字节 + * @param inOff 偏移 + * @param inLen 长度 + * @return 解析后字节 + * @throws InvalidCipherTextException + */ + private byte[] decrypt(byte[] in, int inOff, int inLen) + throws InvalidCipherTextException { + byte[] c1 = new byte[curveLength * 2 + 1]; + + System.arraycopy(in, inOff, c1, 0, c1.length); + + ECPoint c1P = ecParams.getCurve().decodePoint(c1); + + ECPoint s = c1P.multiply(ecParams.getH()); + if (s.isInfinity()) { + throw new InvalidCipherTextException("[h]C1 at infinity"); + } + + c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize(); + + byte[] c2 = new byte[inLen - c1.length - digest.getDigestSize()]; + if (cipherMode == CIPHER_MODE_BC) { + System.arraycopy(in, inOff + c1.length, c2, 0, c2.length); + } else { + // C1 C3 C2 + System.arraycopy(in, inOff + c1.length + digest.getDigestSize(), c2, 0, c2.length); + } + + kdf(digest, c1P, c2); + + byte[] c3 = new byte[digest.getDigestSize()]; + + addFieldElement(digest, c1P.getAffineXCoord()); + digest.update(c2, 0, c2.length); + addFieldElement(digest, c1P.getAffineYCoord()); + + digest.doFinal(c3, 0); + + int check = 0; + // 检查密文输入值C3部分和由摘要生成的C3是否一致 + if (cipherMode == CIPHER_MODE_BC) { + for (int i = 0; i != c3.length; i++) { + check |= c3[i] ^ in[c1.length + c2.length + i]; + } + } else { + for (int i = 0; i != c3.length; i++) { + check |= c3[i] ^ in[c1.length + i]; + } + } + + clearBlock(c1); + clearBlock(c3); + + if (check != 0) { + clearBlock(c2); + throw new InvalidCipherTextException("invalid cipher text"); + } + + return c2; + } + + private boolean notEncrypted(byte[] encData, byte[] in, int inOff) { + for (int i = 0; i != encData.length; i++) { + if (encData[i] != in[inOff]) { + return false; + } + } + + return true; + } + + private void kdf(Digest digest, ECPoint c1, byte[] encData) { + int ct = 1; + int v = digest.getDigestSize(); + + byte[] buf = new byte[digest.getDigestSize()]; + int off = 0; + + for (int i = 1; i <= ((encData.length + v - 1) / v); i++) { + addFieldElement(digest, c1.getAffineXCoord()); + addFieldElement(digest, c1.getAffineYCoord()); + digest.update((byte) (ct >> 24)); + digest.update((byte) (ct >> 16)); + digest.update((byte) (ct >> 8)); + digest.update((byte) ct); + + digest.doFinal(buf, 0); + + if (off + buf.length < encData.length) { + xor(encData, buf, off, buf.length); + } else { + xor(encData, buf, off, encData.length - off); + } + + off += buf.length; + ct++; + } + } + + private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) { + for (int i = 0; i != dRemaining; i++) { + data[dOff + i] ^= kdfOut[i]; + } + } + + private BigInteger nextK() { + int qBitLength = ecParams.getN().bitLength(); + + BigInteger k; + do { + k = new BigInteger(qBitLength, random); + } + while (k.equals(ECConstants.ZERO) || k.compareTo(ecParams.getN()) >= 0); + + return k; + } + + private void addFieldElement(Digest digest, ECFieldElement v) { + byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger()); + + digest.update(p, 0, p.length); + } + + /** + * clear possible sensitive data + */ + private void clearBlock( + byte[] block) { + Arrays.fill(block, (byte) 0); + } + +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm2/SM2KeyPair.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm2/SM2KeyPair.java new file mode 100644 index 0000000..4935044 --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm2/SM2KeyPair.java @@ -0,0 +1,36 @@ +package cn.zyjblogs.starter.common.utils.crypto.sm.sm2; + +public class SM2KeyPair { + /** + * 公钥 + */ + private String publicKey; + /** + * 私钥 + */ + private String privateKey; + + public SM2KeyPair(String publicKey, String privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public SM2KeyPair() { + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm3/SM3.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm3/SM3.java new file mode 100644 index 0000000..a81d23b --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm3/SM3.java @@ -0,0 +1,36 @@ +package cn.zyjblogs.starter.common.utils.crypto.sm.sm3; + +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.util.encoders.Hex; + +import java.nio.charset.StandardCharsets; +import java.util.Locale; + +/** + * 国密SM3摘要算法 + */ +public class SM3 { + + public static String digest(String input) { + + // 创建摘要器 + SM3Digest sm3Digest = new SM3Digest(); + + // 解析输入数据 + byte[] bytes = input.getBytes(StandardCharsets.UTF_8); + + // 构造输出数据缓冲区 + byte[] out = new byte[32]; + + // 设置待摘要字节数据 + sm3Digest.update(bytes, 0, bytes.length); + + // 执行摘要 + sm3Digest.doFinal(out, 0); + + // 返回HEX字符串 + return Hex.toHexString(out).toUpperCase(Locale.ROOT); + + } + +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm4/SM4.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm4/SM4.java new file mode 100644 index 0000000..0bff370 --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/crypto/sm/sm4/SM4.java @@ -0,0 +1,190 @@ +package cn.zyjblogs.starter.common.utils.crypto.sm.sm4; + +import cn.zyjblogs.starter.common.utils.crypto.sm.GmSmException; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.util.encoders.Hex; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.security.Security; +import java.util.Locale; + +/** + * 国密SM4对称加密算法 + */ +public class SM4 { + // 算法 + private static final String SM4_ALGORITHM = "SM4"; + // 密钥长度128位 + private static final int DEFAULT_KEY_SIZE = 128; + // 变换规则(CBC模式) + private static final String TRANSFORMATION_CBC = "SM4/CBC/PKCS5Padding"; + // 变换规则(ECB模式) + private static final String TRANSFORMATION_ECB = "SM4/ECB/PKCS5Padding"; + + // 追加提BC提供器 + static { + Security.addProvider(new BouncyCastleProvider()); + } + + + /** + * 生成默认Key + * + * @return key + */ + public static String generateKey() { + return generateKey(DEFAULT_KEY_SIZE); + } + + + /** + * 生成制定长度Key + * + * @param keySize key 长度 + * @return key + */ + public static String generateKey(int keySize) { + + try { + // 创建Key生成器 + KeyGenerator kg = KeyGenerator.getInstance(SM4_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME); + + // 初始化 + kg.init(keySize, new SecureRandom()); + + // 生成Key + byte[] encoded = kg.generateKey().getEncoded(); + + // 返回HEX字符串 + return Hex.toHexString(encoded).toUpperCase(Locale.ROOT); + } catch (Exception e) { + throw new GmSmException(e); + } + + } + + + /** + * 加密(CBC模式) + * + * @param keyHex 秘钥HEX字符串 + * @param planText 明文字符串 + * @param ivHex 向量HEX字符串 + * @return 加密后的HEX字符串 + */ + public static String encrypt(String keyHex, String planText, String ivHex) { + + try { + // 创建加密对象 + Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC); + // 创建加密规则 + SecretKeySpec keySpec = new SecretKeySpec(Hex.decode(keyHex), SM4_ALGORITHM); + // 创建IV向量 + IvParameterSpec ivSpec = new IvParameterSpec(Hex.decode(ivHex)); + + // 初始化 + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); + + // 调用加密方法 + byte[] outputBytes = cipher.doFinal(planText.getBytes(StandardCharsets.UTF_8)); + + return Hex.toHexString(outputBytes).toUpperCase(Locale.ROOT); + } catch (Exception e) { + throw new GmSmException(e); + } + + } + + /** + * 解密(CBC模式) + * + * @param keyHex 秘钥HEX字符串 + * @param cipherDataHex 密文的HEX字符串 + * @param ivHex 向量HEX字符串 + * @return 解密后的明文 + */ + public static String decrypt(String keyHex, String cipherDataHex, String ivHex) { + + try { + // 创建加密对象 + Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC); + // 创建加密规则 + SecretKeySpec keySpec = new SecretKeySpec(Hex.decode(keyHex), SM4_ALGORITHM); + // 创建IV向量 + IvParameterSpec ivSpec = new IvParameterSpec(Hex.decode(ivHex)); + + // 初始化 + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + // 调用加密方法 + byte[] outputBytes = cipher.doFinal(Hex.decode(cipherDataHex)); + + return new String(outputBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new GmSmException(e); + } + + } + + /** + * 加密(ECB模式) + * + * @param keyHex 秘钥HEX字符串 + * @param planText 明文字符串 + * @return 加密后的HEX字符串 + */ + public static String encrypt(String keyHex, String planText) { + + try { + // 创建加密对象 + Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB); + // 创建加密规则 + SecretKeySpec keySpec = new SecretKeySpec(Hex.decode(keyHex), SM4_ALGORITHM); + + // 初始化 + cipher.init(Cipher.ENCRYPT_MODE, keySpec); + + // 调用加密方法 + byte[] outputBytes = cipher.doFinal(planText.getBytes(StandardCharsets.UTF_8)); + + return Hex.toHexString(outputBytes).toUpperCase(Locale.ROOT); + } catch (Exception e) { + throw new GmSmException(e); + } + + } + + /** + * 解密(ECB模式) + * + * @param keyHex 秘钥HEX字符串 + * @param cipherDataHex 密文的HEX字符串 + * @return 解密后的明文 + */ + public static String decrypt(String keyHex, String cipherDataHex) { + + try { + // 创建加密对象 + Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB); + // 创建加密规则 + SecretKeySpec keySpec = new SecretKeySpec(Hex.decode(keyHex), SM4_ALGORITHM); + + // 初始化 + cipher.init(Cipher.DECRYPT_MODE, keySpec); + + // 调用加密方法 + byte[] outputBytes = cipher.doFinal(Hex.decode(cipherDataHex)); + + return new String(outputBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new GmSmException(e); + } + + } + +}