From 82f982f80929f2bbdb3b70ee5e29d5745a9fd404 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 2 Mar 2020 23:34:45 +0800 Subject: [PATCH] 5.2.0 --- CHANGELOG.md | 4 +- README.md | 10 +- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- .../main/java/cn/hutool/crypto/BCUtil.java | 176 ++------- .../main/java/cn/hutool/crypto/PemUtil.java | 154 ++++++++ .../java/cn/hutool/crypto/asymmetric/SM2.java | 145 ++++--- .../hutool/crypto/asymmetric/SM2Engine.java | 356 ------------------ .../{BCUtilTest.java => PemUtilTest.java} | 27 +- .../java/cn/hutool/crypto/test/SM2Test.java | 19 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 31 files changed, 327 insertions(+), 610 deletions(-) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java delete mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java rename hutool-crypto/src/test/java/cn/hutool/crypto/test/{BCUtilTest.java => PemUtilTest.java} (58%) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0934450c..b990978c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -## 5.1.6 +## 5.2.0 ### 新特性 * 【core 】 NumberUtil.decimalFormat增加Object对象参数支持 @@ -18,6 +18,8 @@ * 【all 】 log、template、tokenizer使用SPI机制代替硬编码 * 【poi 】 Word07Writer增加addPicture * 【crypto】 RSA算法中,BlockSize长度策略调整(issue#721@Github) +* 【crypto】 删除SM2Engine,使用BC库中的对象替代 +* 【crypto】 增加PemUtil工具类 ### Bug修复 diff --git a/README.md b/README.md index 8398ffdb7..986ef43dc 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ -- 主页:https://hutool.cn/ | https://www.hutool.club/ --

- -- QQ群③:555368316 -- + -- QQ群③:555368316 -- -- QQ群④:718802356 --

@@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.1.6 + 5.2.0 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.1.6' +compile 'cn.hutool:hutool-all:5.2.0' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.1.6/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.1.6/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.2.0/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.2.0/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类获工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index 8710cfdff..91ff57278 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.1.6 +5.2.0 diff --git a/docs/js/version.js b/docs/js/version.js index b2c323153..6edc874ca 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.1.6' \ No newline at end of file +var version = '5.2.0' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index dec55c769..5d8370582 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index ac1cbb5b2..e6a967516 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index a8cd0979f..50db780b5 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 0fbd0f039..af290867b 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 9ec5cd7e2..f79791807 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index d5155d798..6869b9e6a 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index e7def7b3c..afccd19fe 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index e76776af0..c95813918 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 59360a4c3..17291e026 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java index 0b423c2ff..91150151e 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java @@ -1,30 +1,25 @@ package cn.hutool.crypto; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.StrUtil; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.ECPointUtil; +import org.bouncycastle.jce.interfaces.ECKey; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.jce.spec.ECNamedCurveSpec; +import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.math.ec.ECCurve; -import org.bouncycastle.util.io.pem.PemObject; -import org.bouncycastle.util.io.pem.PemObjectGenerator; -import org.bouncycastle.util.io.pem.PemReader; -import org.bouncycastle.util.io.pem.PemWriter; -import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; import java.security.GeneralSecurityException; -import java.security.Key; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.ECFieldFp; -import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.ECPublicKeySpec; import java.security.spec.EllipticCurve; @@ -81,7 +76,7 @@ public class BCUtil { final ECPoint point = ECPointUtil.decodePoint(ecCurve, encodeByte); // 根据曲线恢复公钥格式 - ECParameterSpec ecSpec = new ECNamedCurveSpec(curveName, curve, namedSpec.getG(), namedSpec.getN()); + ECNamedCurveSpec ecSpec = new ECNamedCurveSpec(curveName, curve, namedSpec.getG(), namedSpec.getN()); final KeyFactory PubKeyGen = KeyUtil.getKeyFactory("EC"); try { @@ -92,152 +87,37 @@ public class BCUtil { } /** - * 读取PEM格式的私钥 + * ECKey转换为ECKeyParameters * - * @param pemStream pem流 - * @return {@link PrivateKey} - * @since 4.5.2 - */ - public static PrivateKey readPrivateKey(InputStream pemStream) { - return (PrivateKey) readPemKey(pemStream); - } - - /** - * 读取PEM格式的公钥 - * - * @param pemStream pem流 - * @return {@link PublicKey} - * @since 4.5.2 - */ - public static PublicKey readPublicKey(InputStream pemStream) { - return (PublicKey) readPemKey(pemStream); - } - - /** - * 从pem文件中读取公钥或私钥
- * 根据类型返回{@link PublicKey} 或者 {@link PrivateKey} - * - * @param pemKeyStream pem流 - * @return {@link Key} - * @since 4.5.2 - * @deprecated 请使用{@link #readPemKey(InputStream)} - */ - @Deprecated - public static Key readKey(InputStream pemKeyStream) { - return readPemKey(pemKeyStream); - } - - /** - * 从pem文件中读取公钥或私钥
- * 根据类型返回{@link PublicKey} 或者 {@link PrivateKey} - * - * @param keyStream pem流 - * @return {@link Key},null表示无法识别的密钥类型 + * @param ecKey BCECPrivateKey或者BCECPublicKey + * @return ECPrivateKeyParameters或者ECPublicKeyParameters * @since 5.1.6 */ - public static Key readPemKey(InputStream keyStream) { - final PemObject object = readPemObject(keyStream); - final String type = object.getType(); - if (StrUtil.isNotBlank(type)) { - if (type.endsWith("PRIVATE KEY")) { - return KeyUtil.generateRSAPrivateKey(object.getContent()); - } else if (type.endsWith("PUBLIC KEY")) { - return KeyUtil.generateRSAPublicKey(object.getContent()); - } else if (type.endsWith("CERTIFICATE")) { - return KeyUtil.readPublicKeyFromCert(IoUtil.toStream(object.getContent())); - } + public static ECKeyParameters toParams(ECKey ecKey) { + final ECParameterSpec parameterSpec = ecKey.getParameters(); + final ECDomainParameters ecDomainParameters = buildECDomainParameters(parameterSpec); + + if (ecKey instanceof BCECPrivateKey) { + return new ECPrivateKeyParameters(((BCECPrivateKey) ecKey).getD(), ecDomainParameters); + } else if (ecKey instanceof BCECPublicKey) { + return new ECPublicKeyParameters(((BCECPublicKey) ecKey).getQ(), ecDomainParameters); } - //表示无法识别的密钥类型 return null; } /** - * 从pem文件中读取公钥或私钥 + * 构建ECDomainParameters对象 * - * @param keyStream pem流 - * @return 密钥bytes - * @since 4.5.2 - * @deprecated 使用{@link #readPem(InputStream)} - */ - @Deprecated - public static byte[] readKeyBytes(InputStream keyStream) { - return readPem(keyStream); - } - - /** - * 从pem流中读取公钥或私钥 - * - * @param keyStream pem流 - * @return 密钥bytes + * @param parameterSpec ECParameterSpec + * @return ECDomainParameters * @since 5.1.6 */ - public static byte[] readPem(InputStream keyStream) { - PemObject pemObject = readPemObject(keyStream); - if (null != pemObject) { - return pemObject.getContent(); - } - return null; - } - - /** - * 读取pem文件中的信息,包括类型、头信息和密钥内容 - * - * @param keyStream pem流 - * @return {@link PemObject} - * @since 4.5.2 - */ - public static PemObject readPemObject(InputStream keyStream) { - return readPemObject(IoUtil.getUtf8Reader(keyStream)); - } - - /** - * 读取pem文件中的信息,包括类型、头信息和密钥内容 - * - * @param reader pem Reader - * @return {@link PemObject} - * @since 5.1.6 - */ - public static PemObject readPemObject(Reader reader) { - PemReader pemReader = null; - try { - pemReader = new PemReader(reader); - return pemReader.readPemObject(); - } catch (IOException e) { - throw new IORuntimeException(e); - } finally { - IoUtil.close(pemReader); - } - } - - /** - * 写出pem密钥(私钥、公钥、证书) - * - * @param type 密钥类型(私钥、公钥、证书) - * @param content 密钥内容 - * @param keyStream pem流 - * @since 5.1.6 - */ - public static void writePemObject(String type, byte[] content, OutputStream keyStream) { - writePemObject(new PemObject(type, content), keyStream); - } - - /** - * 写出pem密钥(私钥、公钥、证书) - * - * @param pemObject pem对象,包括密钥和密钥类型等信息 - * @param keyStream pem流 - * @since 5.1.6 - */ - public static void writePemObject(PemObjectGenerator pemObject, OutputStream keyStream) { - PemWriter writer = null; - try { - writer = new PemWriter(IoUtil.getUtf8Writer(keyStream)); - writer.writeObject(pemObject); - } catch (IOException e) { - throw new IORuntimeException(e); - } finally { - IoUtil.close(writer); - } + public static ECDomainParameters buildECDomainParameters(ECParameterSpec parameterSpec) { + return new ECDomainParameters( + parameterSpec.getCurve(), + parameterSpec.getG(), + parameterSpec.getN(), + parameterSpec.getH()); } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java new file mode 100644 index 000000000..d2b8930ed --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java @@ -0,0 +1,154 @@ +package cn.hutool.crypto; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemObjectGenerator; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.util.io.pem.PemWriter; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * PEM(Privacy Enhanced Mail)格式相关工具类。 + * + *

+ * PEM一般为文本格式,以 -----BEGIN... 开头,以 -----END... 结尾,中间的内容是 BASE64 编码。 + *

+ * 这种格式可以保存证书和私钥,有时我们也把PEM格式的私钥的后缀改为 .key 以区别证书与私钥。 + * + * @author looly + * @since 5.1.6 + */ +public class PemUtil { + + /** + * 读取PEM格式的私钥 + * + * @param pemStream pem流 + * @return {@link PrivateKey} + * @since 4.5.2 + */ + public static PrivateKey readPemPrivateKey(InputStream pemStream) { + return (PrivateKey) PemUtil.readPemKey(pemStream); + } + + /** + * 读取PEM格式的公钥 + * + * @param pemStream pem流 + * @return {@link PublicKey} + * @since 4.5.2 + */ + public static PublicKey readPemPublicKey(InputStream pemStream) { + return (PublicKey) PemUtil.readPemKey(pemStream); + } + + /** + * 从pem文件中读取公钥或私钥
+ * 根据类型返回 {@link PublicKey} 或者 {@link PrivateKey} + * + * @param keyStream pem流 + * @return {@link Key},null表示无法识别的密钥类型 + * @since 5.1.6 + */ + public static Key readPemKey(InputStream keyStream) { + final PemObject object = readPemObject(keyStream); + final String type = object.getType(); + if (StrUtil.isNotBlank(type)) { + if (type.endsWith("PRIVATE KEY")) { + return KeyUtil.generateRSAPrivateKey(object.getContent()); + } else if (type.endsWith("PUBLIC KEY")) { + return KeyUtil.generateRSAPublicKey(object.getContent()); + } else if (type.endsWith("CERTIFICATE")) { + return KeyUtil.readPublicKeyFromCert(IoUtil.toStream(object.getContent())); + } + } + + //表示无法识别的密钥类型 + return null; + } + + /** + * 从pem流中读取公钥或私钥 + * + * @param keyStream pem流 + * @return 密钥bytes + * @since 5.1.6 + */ + public static byte[] readPem(InputStream keyStream) { + PemObject pemObject = readPemObject(keyStream); + if (null != pemObject) { + return pemObject.getContent(); + } + return null; + } + + /** + * 读取pem文件中的信息,包括类型、头信息和密钥内容 + * + * @param keyStream pem流 + * @return {@link PemObject} + * @since 4.5.2 + */ + public static PemObject readPemObject(InputStream keyStream) { + return readPemObject(IoUtil.getUtf8Reader(keyStream)); + } + + /** + * 读取pem文件中的信息,包括类型、头信息和密钥内容 + * + * @param reader pem Reader + * @return {@link PemObject} + * @since 5.1.6 + */ + public static PemObject readPemObject(Reader reader) { + PemReader pemReader = null; + try { + pemReader = new PemReader(reader); + return pemReader.readPemObject(); + } catch (IOException e) { + throw new IORuntimeException(e); + } finally { + IoUtil.close(pemReader); + } + } + + /** + * 写出pem密钥(私钥、公钥、证书) + * + * @param type 密钥类型(私钥、公钥、证书) + * @param content 密钥内容 + * @param keyStream pem流 + * @since 5.1.6 + */ + public static void writePemObject(String type, byte[] content, OutputStream keyStream) { + writePemObject(new PemObject(type, content), keyStream); + } + + /** + * 写出pem密钥(私钥、公钥、证书) + * + * @param pemObject pem对象,包括密钥和密钥类型等信息 + * @param keyStream pem流 + * @since 5.1.6 + */ + public static void writePemObject(PemObjectGenerator pemObject, OutputStream keyStream) { + PemWriter writer = null; + try { + writer = new PemWriter(IoUtil.getUtf8Writer(keyStream)); + writer.writeObject(pemObject); + } catch (IOException e) { + throw new IORuntimeException(e); + } finally { + IoUtil.close(writer); + } + } +} diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 3dc10d01c..da3a1b213 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -2,8 +2,9 @@ package cn.hutool.crypto.asymmetric; import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.asymmetric.SM2Engine.SM2Mode; import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.SM2Engine; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.ParametersWithID; @@ -19,23 +20,26 @@ import java.security.PublicKey; * 国密SM2算法实现,基于BC库
* SM2算法只支持公钥加密,私钥解密
* 参考:https://blog.csdn.net/pridas/article/details/86118774 - * + * * @author looly * @since 4.3.2 */ public class SM2 extends AbstractAsymmetricCrypto { - /** 算法EC */ + /** + * 算法EC + */ private static final String ALGORITHM_SM2 = "SM2"; protected SM2Engine engine; protected SM2Signer signer; - private SM2Mode mode; + private SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2; private ECPublicKeyParameters publicKeyParams; private ECPrivateKeyParameters privateKeyParams; // ------------------------------------------------------------------ Constructor start + /** * 构造,生成新的私钥公钥对 */ @@ -47,9 +51,9 @@ public class SM2 extends AbstractAsymmetricCrypto { * 构造
* 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * + * * @param privateKeyStr 私钥Hex或Base64表示 - * @param publicKeyStr 公钥Hex或Base64表示 + * @param publicKeyStr 公钥Hex或Base64表示 */ public SM2(String privateKeyStr, String publicKeyStr) { this(SecureUtil.decode(privateKeyStr), SecureUtil.decode(publicKeyStr)); @@ -59,9 +63,9 @@ public class SM2 extends AbstractAsymmetricCrypto { * 构造
* 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * + * * @param privateKey 私钥 - * @param publicKey 公钥 + * @param publicKey 公钥 */ public SM2(byte[] privateKey, byte[] publicKey) { this(// @@ -74,9 +78,9 @@ public class SM2 extends AbstractAsymmetricCrypto { * 构造
* 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * + * * @param privateKey 私钥 - * @param publicKey 公钥 + * @param publicKey 公钥 */ public SM2(PrivateKey privateKey, PublicKey publicKey) { super(ALGORITHM_SM2, privateKey, publicKey); @@ -87,9 +91,9 @@ public class SM2 extends AbstractAsymmetricCrypto { * 初始化
* 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密(签名)或者解密(校验) - * + * * @param privateKey 私钥 - * @param publicKey 公钥 + * @param publicKey 公钥 * @return this */ public SM2 init(PrivateKey privateKey, PublicKey publicKey) { @@ -103,16 +107,17 @@ public class SM2 extends AbstractAsymmetricCrypto { } // --------------------------------------------------------------------------------- Encrypt + /** * 加密,SM2非对称加密的结果由C1,C2,C3三部分组成,其中: - * + * *

 	 * C1 生成随机数的计算出的椭圆曲线点
 	 * C2 密文数据
 	 * C3 SM3的摘要值
 	 * 
- * - * @param data 被加密的bytes + * + * @param data 被加密的bytes * @param keyType 私钥或公钥 {@link KeyType} * @return 加密后的bytes * @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常 @@ -123,22 +128,43 @@ public class SM2 extends AbstractAsymmetricCrypto { throw new IllegalArgumentException("Encrypt is only support by public key"); } checkKey(keyType); + return encrypt(data, new ParametersWithRandom(getCipherParameters(keyType))); + } + /** + * 加密,SM2非对称加密的结果由C1,C2,C3三部分组成,其中: + * + *
+	 * C1 生成随机数的计算出的椭圆曲线点
+	 * C2 密文数据
+	 * C3 SM3的摘要值
+	 * 
+ * + * @param data 被加密的bytes + * @param pubKeyParameters 公钥参数 + * @return 加密后的bytes + * @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常 + * @since 5.1.6 + */ + public byte[] encrypt(byte[] data, CipherParameters pubKeyParameters) throws CryptoException { lock.lock(); final SM2Engine engine = getEngine(); try { - engine.init(true, new ParametersWithRandom(getCipherParameters(keyType))); + engine.init(true, pubKeyParameters); return engine.processBlock(data, 0, data.length); + } catch (InvalidCipherTextException e) { + throw new CryptoException(e); } finally { lock.unlock(); } } // --------------------------------------------------------------------------------- Decrypt + /** * 解密 - * - * @param data SM2密文,实际包含三部分:ECC公钥、真正的密文、公钥和原文的SM3-HASH值 + * + * @param data SM2密文,实际包含三部分:ECC公钥、真正的密文、公钥和原文的SM3-HASH值 * @param keyType 私钥或公钥 {@link KeyType} * @return 加密后的bytes * @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常 @@ -149,21 +175,35 @@ public class SM2 extends AbstractAsymmetricCrypto { throw new IllegalArgumentException("Decrypt is only support by private key"); } checkKey(keyType); + return decrypt(data, getCipherParameters(keyType)); + } + /** + * 解密 + * + * @param data SM2密文,实际包含三部分:ECC公钥、真正的密文、公钥和原文的SM3-HASH值 + * @param privateKeyParameters 私钥参数 + * @return 加密后的bytes + * @throws CryptoException 包括InvalidKeyException和InvalidCipherTextException的包装异常 + * @since 5.1.6 + */ + public byte[] decrypt(byte[] data, CipherParameters privateKeyParameters) throws CryptoException { lock.lock(); final SM2Engine engine = getEngine(); try { - engine.init(false, getCipherParameters(keyType)); + engine.init(false, privateKeyParameters); return engine.processBlock(data, 0, data.length); + } catch (InvalidCipherTextException e) { + throw new CryptoException(e); } finally { lock.unlock(); } } - // --------------------------------------------------------------------------------- Sign and Verify + /** * 用私钥对信息生成数字签名 - * + * * @param data 加密数据 * @return 签名 */ @@ -173,9 +213,9 @@ public class SM2 extends AbstractAsymmetricCrypto { /** * 用私钥对信息生成数字签名 - * + * * @param data 加密数据 - * @param id 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() + * @param id 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() * @return 签名 */ public byte[] sign(byte[] data, byte[] id) { @@ -198,7 +238,7 @@ public class SM2 extends AbstractAsymmetricCrypto { /** * 用公钥检验数字签名的合法性 - * + * * @param data 数据 * @param sign 签名 * @return 是否验证通过 @@ -209,10 +249,10 @@ public class SM2 extends AbstractAsymmetricCrypto { /** * 用公钥检验数字签名的合法性 - * + * * @param data 数据 * @param sign 签名 - * @param id 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() + * @param id 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() * @return 是否验证通过 */ public boolean verify(byte[] data, byte[] sign, byte[] id) { @@ -257,22 +297,23 @@ public class SM2 extends AbstractAsymmetricCrypto { /** * 设置加密类型 - * - * @param mode {@link SM2Mode} + * + * @param mode {@link SM2Engine.Mode} * @return this */ - public SM2 setMode(SM2Mode mode) { + public SM2 setMode(SM2Engine.Mode mode) { this.mode = mode; if (null != this.engine) { - this.engine.setMode(mode); + this.engine = null; } return this; } // ------------------------------------------------------------------------------------------------------------------------- Private method start + /** * 初始化加密解密参数(包括私钥和公钥参数) - * + * * @return this */ private SM2 initCipherParams() { @@ -292,16 +333,16 @@ public class SM2 extends AbstractAsymmetricCrypto { /** * 获取密钥类型对应的加密参数对象{@link CipherParameters} - * + * * @param keyType Key类型枚举,包括私钥或公钥 * @return {@link CipherParameters} */ private CipherParameters getCipherParameters(KeyType keyType) { switch (keyType) { - case PublicKey: - return this.publicKeyParams; - case PrivateKey: - return this.privateKeyParams; + case PublicKey: + return this.publicKeyParams; + case PrivateKey: + return this.privateKeyParams; } return null; @@ -309,27 +350,27 @@ public class SM2 extends AbstractAsymmetricCrypto { /** * 检查对应类型的Key是否存在 - * + * * @param keyType key类型 */ private void checkKey(KeyType keyType) { switch (keyType) { - case PublicKey: - if (null == this.publicKey) { - throw new NullPointerException("No public key provided"); - } - break; - case PrivateKey: - if (null == this.privateKey) { - throw new NullPointerException("No private key provided"); - } - break; + case PublicKey: + if (null == this.publicKey) { + throw new NullPointerException("No public key provided"); + } + break; + case PrivateKey: + if (null == this.privateKey) { + throw new NullPointerException("No private key provided"); + } + break; } } /** - * 获取{@link SM2Engine} - * + * 获取{@link SM2Engine},此对象为懒加载模式 + * * @return {@link SM2Engine} */ private SM2Engine getEngine() { @@ -338,10 +379,10 @@ public class SM2 extends AbstractAsymmetricCrypto { } return this.engine; } - + /** - * 获取{@link SM2Signer} - * + * 获取{@link SM2Signer},此对象为懒加载模式 + * * @return {@link SM2Signer} */ private SM2Signer getSigner() { diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java deleted file mode 100644 index abe4bf6d4..000000000 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2Engine.java +++ /dev/null @@ -1,356 +0,0 @@ -package cn.hutool.crypto.asymmetric; - -import java.math.BigInteger; -import java.util.Random; - -import org.bouncycastle.crypto.CipherParameters; -import org.bouncycastle.crypto.CryptoServicesRegistrar; -import org.bouncycastle.crypto.Digest; -import org.bouncycastle.crypto.digests.SM3Digest; -import org.bouncycastle.crypto.params.ECDomainParameters; -import org.bouncycastle.crypto.params.ECKeyParameters; -import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.params.ECPublicKeyParameters; -import org.bouncycastle.crypto.params.ParametersWithRandom; -import org.bouncycastle.math.ec.ECConstants; -import org.bouncycastle.math.ec.ECFieldElement; -import org.bouncycastle.math.ec.ECMultiplier; -import org.bouncycastle.math.ec.ECPoint; -import org.bouncycastle.math.ec.FixedPointCombMultiplier; -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.BigIntegers; -import org.bouncycastle.util.Memoable; -import org.bouncycastle.util.Pack; - -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.crypto.CryptoException; - -/** - * SM2加密解密引擎,来自Bouncy Castle库的SM2Engine类改造
- * SM2加密后的数据格式为(两种模式): - * - *
- * curve(C1) | data(C2) | digest(C3)
- * curve(C1) | digest(C3) | data(C2)
- * 
- * - * @author looly, bouncycastle - * @since 4.5.0 - */ -public class SM2Engine { - - private final Digest digest; - - private boolean forEncryption; - private ECKeyParameters ecKey; - private ECDomainParameters ecParams; - private int curveLength; - private Random random; - /** 加密解密模式 */ - private SM2Mode mode; - - /** - * 构造 - */ - public SM2Engine() { - this(new SM3Digest()); - } - - /** - * 构造 - * - * @param mode SM2密钥生成模式,可选C1C2C3和C1C3C2 - */ - public SM2Engine(SM2Mode mode) { - this(new SM3Digest(), mode); - } - - /** - * 构造 - * - * @param digest 摘要算法啊 - */ - public SM2Engine(Digest digest) { - this(digest, null); - } - - /** - * 构造 - * - * @param digest 摘要算法啊 - * @param mode SM2密钥生成模式,可选C1C2C3和C1C3C2 - */ - public SM2Engine(Digest digest, SM2Mode mode) { - this.digest = digest; - this.mode = ObjectUtil.defaultIfNull(mode, SM2Mode.C1C3C2); - } - - /** - * 初始化引擎 - * - * @param forEncryption 是否为加密模式 - * @param param {@link CipherParameters},此处应为{@link ParametersWithRandom}(加密时)或{@link ECKeyParameters}(解密时) - */ - public void init(boolean forEncryption, CipherParameters param) { - this.forEncryption = forEncryption; - - if (param instanceof ParametersWithRandom) { - final ParametersWithRandom rParam = (ParametersWithRandom) param; - this.ecKey = (ECKeyParameters) rParam.getParameters(); - this.random = rParam.getRandom(); - } else { - this.ecKey = (ECKeyParameters) param; - } - this.ecParams = this.ecKey.getParameters(); - - if (forEncryption) { - // 检查曲线点 - final ECPoint ecPoint = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH()); - if (ecPoint.isInfinity()) { - throw new IllegalArgumentException("invalid key: [h]Q at infinity"); - } - - // 检查随机参数 - if (null == this.random) { - this.random = CryptoServicesRegistrar.getSecureRandom(); - } - } - - // 曲线位长度 - this.curveLength = (this.ecParams.getCurve().getFieldSize() + 7) / 8; - } - - /** - * 处理块,包括加密和解密 - * - * @param in 数据 - * @param inOff 数据开始位置 - * @param inLen 数据长度 - * @return 结果 - */ - public byte[] processBlock(byte[] in, int inOff, int inLen) { - if (forEncryption) { - return encrypt(in, inOff, inLen); - } else { - return decrypt(in, inOff, inLen); - } - } - - /** - * 设置加密类型 - * - * @param mode {@link SM2Mode} - * @return this - */ - public SM2Engine setMode(SM2Mode mode) { - this.mode = mode; - return this; - } - - /** - * SM2算法模式
- * 在SM2算法中,C1C2C3为旧标准模式,C1C3C2为新标准模式 - * - * @author looly - * - */ - public enum SM2Mode { - C1C2C3, C1C3C2 - } - - protected ECMultiplier createBasePointMultiplier() { - return new FixedPointCombMultiplier(); - } - - // --------------------------------------------------------------------------------------------------- Private method start - /** - * 加密 - * - * @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); - - final ECMultiplier multiplier = createBasePointMultiplier(); - - byte[] c1; - ECPoint kPB; - BigInteger k; - do { - k = nextK(); - // 产生随机数计算出曲线点C1 - c1 = multiplier.multiply(ecParams.getG(), k).normalize().getEncoded(false); - kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize(); - kdf(kPB, c2); - } while (notEncrypted(c2, in, inOff)); - - // 杂凑值,效验数据 - byte[] c3 = new byte[digest.getDigestSize()]; - - addFieldElement(kPB.getAffineXCoord()); - this.digest.update(in, inOff, inLen); - addFieldElement(kPB.getAffineYCoord()); - - this.digest.doFinal(c3, 0); - - // 按照对应模式输出结果 - if (mode == SM2Mode.C1C3C2) { - return Arrays.concatenate(c1, c3, c2); - } - return Arrays.concatenate(c1, c2, c3); - } - - /** - * 解密,只支持私钥解密 - * - * @param in 密文 - * @param inOff 位置 - * @param inLen 长度 - * @return 解密后的内容 - */ - private byte[] decrypt(byte[] in, int inOff, int inLen) { - // 获取曲线点 - final byte[] c1 = new byte[this.curveLength * 2 + 1]; - System.arraycopy(in, inOff, c1, 0, c1.length); - - ECPoint c1P = this.ecParams.getCurve().decodePoint(c1); - if (c1P.multiply(this.ecParams.getH()).isInfinity()) { - throw new CryptoException("[h]C1 at infinity"); - } - c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize(); - - final int digestSize = this.digest.getDigestSize(); - - // 解密C2数据 - final byte[] c2 = new byte[inLen - c1.length - digestSize]; - - if (SM2Mode.C1C3C2 == this.mode) { - // C2位于第三部分 - System.arraycopy(in, inOff + c1.length + digestSize, c2, 0, c2.length); - } else { - // C2位于第二部分 - System.arraycopy(in, inOff + c1.length, c2, 0, c2.length); - } - kdf(c1P, c2); - - // 使用摘要验证C2数据 - final byte[] c3 = new byte[digestSize]; - - addFieldElement(c1P.getAffineXCoord()); - this.digest.update(c2, 0, c2.length); - addFieldElement(c1P.getAffineYCoord()); - this.digest.doFinal(c3, 0); - - int check = 0; - for (int i = 0; i != c3.length; i++) { - check |= c3[i] ^ in[inOff + c1.length + ((SM2Mode.C1C3C2 == this.mode) ? 0 : c2.length) + i]; - } - - Arrays.fill(c1, (byte) 0); - Arrays.fill(c3, (byte) 0); - - if (check != 0) { - Arrays.fill(c2, (byte) 0); - throw new CryptoException("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 + i]) { - return false; - } - } - return true; - } - - /** - * 解密数据 - * - * @param c1 c1点 - * @param encData 密文 - */ - private void kdf(ECPoint c1, byte[] encData) { - final Digest digest = this.digest; - int digestSize = digest.getDigestSize(); - byte[] buf = new byte[Math.max(4, digestSize)]; - int off = 0; - - Memoable memo = null; - Memoable copy = null; - - if (digest instanceof Memoable) { - addFieldElement(c1.getAffineXCoord()); - addFieldElement(c1.getAffineYCoord()); - memo = (Memoable) digest; - copy = memo.copy(); - } - - int ct = 0; - - while (off < encData.length) { - if (memo != null) { - memo.reset(copy); - } else { - addFieldElement(c1.getAffineXCoord()); - addFieldElement(c1.getAffineYCoord()); - } - - Pack.intToBigEndian(++ct, buf, 0); - digest.update(buf, 0, 4); - digest.doFinal(buf, 0); - - int xorLen = Math.min(digestSize, encData.length - off); - xor(encData, buf, off, xorLen); - off += xorLen; - } - } - - /** - * 异或 - * - * @param data 数据 - * @param kdfOut kdf输出值 - * @param dOff d偏移 - * @param dRemaining d剩余 - */ - private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) { - for (int i = 0; i != dRemaining; i++) { - data[dOff + i] ^= kdfOut[i]; - } - } - - /** - * 下一个K值 - * - * @return K值 - */ - private BigInteger nextK() { - final int qBitLength = this.ecParams.getN().bitLength(); - - BigInteger k; - do { - k = new BigInteger(qBitLength, this.random); - } while (k.equals(ECConstants.ZERO) || k.compareTo(this.ecParams.getN()) >= 0); - - return k; - } - - /** - * 增加字段节点 - * - * @param v 节点 - */ - private void addFieldElement(ECFieldElement v) { - final byte[] p = BigIntegers.asUnsignedByteArray(this.curveLength, v.toBigInteger()); - this.digest.update(p, 0, p.length); - } - // --------------------------------------------------------------------------------------------------- Private method start -} diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/PemUtilTest.java similarity index 58% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/test/PemUtilTest.java index 9b8ab803a..7db403b1b 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/PemUtilTest.java @@ -1,40 +1,39 @@ package cn.hutool.crypto.test; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.crypto.PemUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import org.junit.Assert; +import org.junit.Test; + import java.security.PrivateKey; import java.security.PublicKey; -import org.junit.Assert; -import org.junit.Test; +public class PemUtilTest { -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.crypto.BCUtil; -import cn.hutool.crypto.asymmetric.KeyType; -import cn.hutool.crypto.asymmetric.RSA; - -public class BCUtilTest { - @Test public void readPrivateKeyTest() { - PrivateKey privateKey = BCUtil.readPrivateKey(ResourceUtil.getStream("test_private_key.pem")); + PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_private_key.pem")); Assert.assertNotNull(privateKey); } @Test public void readPublicKeyTest() { - PublicKey publicKey = BCUtil.readPublicKey(ResourceUtil.getStream("test_public_key.csr")); + PublicKey publicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_public_key.csr")); Assert.assertNotNull(publicKey); } @Test public void readPemKeyTest() { - PublicKey publicKey = (PublicKey) BCUtil.readPemKey(ResourceUtil.getStream("test_public_key.csr")); + PublicKey publicKey = (PublicKey) PemUtil.readPemKey(ResourceUtil.getStream("test_public_key.csr")); Assert.assertNotNull(publicKey); } @Test public void validateKey() { - PrivateKey privateKey = BCUtil.readPrivateKey(ResourceUtil.getStream("test_private_key.pem")); - PublicKey publicKey = BCUtil.readPublicKey(ResourceUtil.getStream("test_public_key.csr")); + PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_private_key.pem")); + PublicKey publicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_public_key.csr")); RSA rsa = new RSA(privateKey, publicKey); String str = "你好,Hutool";//测试字符串 diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java index eabd71f5a..a119ddb23 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java @@ -1,13 +1,5 @@ package cn.hutool.crypto.test; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; - -import cn.hutool.core.lang.Console; -import org.junit.Assert; -import org.junit.Test; - import cn.hutool.core.codec.Base64; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.HexUtil; @@ -17,7 +9,13 @@ import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.SM2; -import cn.hutool.crypto.asymmetric.SM2Engine.SM2Mode; +import org.bouncycastle.crypto.engines.SM2Engine; +import org.junit.Assert; +import org.junit.Test; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; /** * SM2算法单元测试 @@ -48,10 +46,9 @@ public class SM2Test { KeyPair pair = SecureUtil.generateKeyPair("SM2"); byte[] privateKey = pair.getPrivate().getEncoded(); byte[] publicKey = pair.getPublic().getEncoded(); - Console.log(HexUtil.encodeHexStr(publicKey)); SM2 sm2 = SmUtil.sm2(privateKey, publicKey); - sm2.setMode(SM2Mode.C1C3C2); + sm2.setMode(SM2Engine.Mode.C1C2C3); // 公钥加密,私钥解密 byte[] encrypt = sm2.encrypt(StrUtil.bytes("我是一段测试aaaa", CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey); diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 9c6b609fa..0a121e96c 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 183536a83..099d877d6 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index d2bf56cca..06036ed4e 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 2457f4bad..d101a8938 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index a83553ded..3c44338c6 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index e19e14b12..6e1c975cb 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6abbe8acf..fdc1acc8e 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 01ebf41f7..1010d9711 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 50f19e113..e93167157 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index d1eee8ff9..432ad0ce7 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index c95094e04..18b5f09d5 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 48a1bb296..ec167b5b4 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.1.6-SNAPSHOT + 5.2.0-SNAPSHOT hutool 提供丰富的Java工具方法 https://github.com/looly/hutool