diff --git a/CHANGELOG.md b/CHANGELOG.md
index 78d91904e..7c4417f34 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,10 +17,11 @@
* 【http 】 修复HttpDownloader.downloadFile 方法缺少static问题(issue#I6Z8VU@Gitee)
-------------------------------------------------------------------------------------------------------------
-# 5.8.18 (2023-04-16)
+# 5.8.18 (2023-05-04)
### 🐣新特性
* 【extra 】 JschUtil新增一个重载方法以支持私钥以byte数组形式载入(pr#3057@Github)
+* 【crypto】 优化MD5性能(issue#I6ZIQH@Gitee)
### 🐞Bug修复
* 【core 】 修复CollUtil.reverseNew针对非可变列表异常(issue#3056@Github)
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/SecureUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/SecureUtil.java
index 40b07fb87..b27280777 100755
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/SecureUtil.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/SecureUtil.java
@@ -1057,6 +1057,20 @@ public class SecureUtil {
return messageDigest;
}
+ /**
+ * 创建{@link MessageDigest},使用JDK默认的Provider
+ *
+ * @param algorithm 算法
+ * @return {@link MessageDigest}
+ */
+ public static MessageDigest createJdkMessageDigest(final String algorithm) {
+ try {
+ return MessageDigest.getInstance(algorithm);
+ } catch (final NoSuchAlgorithmException e) {
+ throw new CryptoException(e);
+ }
+ }
+
/**
* 创建{@link Mac}
*
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Digester.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Digester.java
index 8bad73888..84275d694 100755
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Digester.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Digester.java
@@ -22,7 +22,7 @@ import java.security.Provider;
/**
* 摘要算法
* 注意:此对象实例化后为非线程安全!
- *
+ *
* @author Looly
*
*/
@@ -40,7 +40,7 @@ public class Digester implements Serializable {
// ------------------------------------------------------------------------------------------- Constructor start
/**
* 构造
- *
+ *
* @param algorithm 算法枚举
*/
public Digester(DigestAlgorithm algorithm) {
@@ -49,7 +49,7 @@ public class Digester implements Serializable {
/**
* 构造
- *
+ *
* @param algorithm 算法枚举
*/
public Digester(String algorithm) {
@@ -58,7 +58,7 @@ public class Digester implements Serializable {
/**
* 构造
- *
+ *
* @param algorithm 算法
* @param provider 算法提供者,null表示JDK默认,可以引入Bouncy Castle等来提供更多算法支持
* @since 4.5.1
@@ -69,7 +69,7 @@ public class Digester implements Serializable {
/**
* 构造
- *
+ *
* @param algorithm 算法
* @param provider 算法提供者,null表示JDK默认,可以引入Bouncy Castle等来提供更多算法支持
* @since 4.5.1
@@ -77,11 +77,20 @@ public class Digester implements Serializable {
public Digester(String algorithm, Provider provider) {
init(algorithm, provider);
}
+
+ /**
+ * 构造
+ *
+ * @param messageDigest {@link MessageDigest}
+ */
+ public Digester(final MessageDigest messageDigest) {
+ this.digest = messageDigest;
+ }
// ------------------------------------------------------------------------------------------- Constructor end
/**
* 初始化
- *
+ *
* @param algorithm 算法
* @param provider 算法提供者,null表示JDK默认,可以引入Bouncy Castle等来提供更多算法支持
* @return Digester
@@ -99,10 +108,10 @@ public class Digester implements Serializable {
}
return this;
}
-
+
/**
* 设置加盐内容
- *
+ *
* @param salt 盐值
* @return this
* @since 4.4.3
@@ -115,18 +124,18 @@ public class Digester implements Serializable {
/**
* 设置加盐的位置,只有盐值存在时有效
* 加盐的位置指盐位于数据byte数组中的位置,例如:
- *
+ *
*
* data: 0123456
*
- *
+ *
* 则当saltPosition = 2时,盐位于data的1和2中间,即第二个空隙,即:
- *
+ *
*
* data: 01[salt]23456
*
- *
- *
+ *
+ *
* @param saltPosition 盐的位置
* @return this
* @since 4.4.3
@@ -138,7 +147,7 @@ public class Digester implements Serializable {
/**
* 设置重复计算摘要值次数
- *
+ *
* @param digestCount 摘要值次数
* @return this
*/
@@ -149,7 +158,7 @@ public class Digester implements Serializable {
/**
* 重置{@link MessageDigest}
- *
+ *
* @return this
* @since 4.5.1
*/
@@ -161,7 +170,7 @@ public class Digester implements Serializable {
// ------------------------------------------------------------------------------------------- Digest
/**
* 生成文件摘要
- *
+ *
* @param data 被摘要数据
* @param charsetName 编码
* @return 摘要
@@ -169,10 +178,10 @@ public class Digester implements Serializable {
public byte[] digest(String data, String charsetName) {
return digest(data, CharsetUtil.charset(charsetName));
}
-
+
/**
* 生成文件摘要
- *
+ *
* @param data 被摘要数据
* @param charset 编码
* @return 摘要
@@ -184,7 +193,7 @@ public class Digester implements Serializable {
/**
* 生成文件摘要
- *
+ *
* @param data 被摘要数据
* @return 摘要
*/
@@ -194,7 +203,7 @@ public class Digester implements Serializable {
/**
* 生成文件摘要,并转为16进制字符串
- *
+ *
* @param data 被摘要数据
* @param charsetName 编码
* @return 摘要
@@ -202,10 +211,10 @@ public class Digester implements Serializable {
public String digestHex(String data, String charsetName) {
return digestHex(data, CharsetUtil.charset(charsetName));
}
-
+
/**
* 生成文件摘要,并转为16进制字符串
- *
+ *
* @param data 被摘要数据
* @param charset 编码
* @return 摘要
@@ -217,7 +226,7 @@ public class Digester implements Serializable {
/**
* 生成文件摘要
- *
+ *
* @param data 被摘要数据
* @return 摘要
*/
@@ -228,7 +237,7 @@ public class Digester implements Serializable {
/**
* 生成文件摘要
* 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
- *
+ *
* @param file 被摘要文件
* @return 摘要bytes
* @throws CryptoException Cause by IOException
@@ -246,7 +255,7 @@ public class Digester implements Serializable {
/**
* 生成文件摘要,并转为16进制字符串
* 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
- *
+ *
* @param file 被摘要文件
* @return 摘要
*/
@@ -256,7 +265,7 @@ public class Digester implements Serializable {
/**
* 生成摘要,考虑加盐和重复摘要次数
- *
+ *
* @param data 数据bytes
* @return 摘要bytes
*/
@@ -284,7 +293,7 @@ public class Digester implements Serializable {
/**
* 生成摘要,并转为16进制字符串
- *
+ *
* @param data 被摘要数据
* @return 摘要
*/
@@ -294,7 +303,7 @@ public class Digester implements Serializable {
/**
* 生成摘要,使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
- *
+ *
* @param data {@link InputStream} 数据流
* @return 摘要bytes
*/
@@ -305,7 +314,7 @@ public class Digester implements Serializable {
/**
* 生成摘要,并转为16进制字符串
* 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
- *
+ *
* @param data 被摘要数据
* @return 摘要
*/
@@ -315,7 +324,7 @@ public class Digester implements Serializable {
/**
* 生成摘要
- *
+ *
* @param data {@link InputStream} 数据流
* @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要bytes
@@ -325,7 +334,7 @@ public class Digester implements Serializable {
if (bufferLength < 1) {
bufferLength = IoUtil.DEFAULT_BUFFER_SIZE;
}
-
+
byte[] result;
try {
if (ArrayUtil.isEmpty(this.salt)) {
@@ -336,14 +345,14 @@ public class Digester implements Serializable {
} catch (IOException e) {
throw new IORuntimeException(e);
}
-
+
return resetAndRepeatDigest(result);
}
/**
* 生成摘要,并转为16进制字符串
* 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
- *
+ *
* @param data 被摘要数据
* @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要
@@ -354,7 +363,7 @@ public class Digester implements Serializable {
/**
* 获得 {@link MessageDigest}
- *
+ *
* @return {@link MessageDigest}
*/
public MessageDigest getDigest() {
@@ -363,7 +372,7 @@ public class Digester implements Serializable {
/**
* 获取散列长度,0表示不支持此方法
- *
+ *
* @return 散列长度,0表示不支持此方法
* @since 4.5.0
*/
@@ -374,7 +383,7 @@ public class Digester implements Serializable {
// -------------------------------------------------------------------------------- Private method start
/**
* 生成摘要
- *
+ *
* @param data {@link InputStream} 数据流
* @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要bytes
@@ -391,7 +400,7 @@ public class Digester implements Serializable {
/**
* 生成摘要
- *
+ *
* @param data {@link InputStream} 数据流
* @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要bytes
@@ -430,7 +439,7 @@ public class Digester implements Serializable {
/**
* 生成摘要
- *
+ *
* @param datas 数据bytes
* @return 摘要bytes
* @since 4.4.3
@@ -447,7 +456,7 @@ public class Digester implements Serializable {
/**
* 重复计算摘要,取决于{@link #digestCount} 值
* 每次计算摘要前都会重置{@link #digest}
- *
+ *
* @param digestData 第一次摘要过的数据
* @return 摘要
*/
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigesterFactory.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigesterFactory.java
new file mode 100644
index 000000000..1994d7de8
--- /dev/null
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigesterFactory.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2023 looly(loolly@aliyun.com)
+ * Hutool is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package cn.hutool.crypto.digest;
+
+import cn.hutool.crypto.GlobalBouncyCastleProvider;
+import cn.hutool.crypto.SecureUtil;
+
+import java.security.MessageDigest;
+
+/**
+ * {@link Digester}创建简单工厂,用于生产{@link Digester}对象
+ * 参考Guava方式,工厂负责持有一个原始的{@link MessageDigest}对象,使用时优先通过clone方式创建对象,提高初始化性能。
+ *
+ * @author looly
+ */
+public class DigesterFactory {
+
+ /**
+ * 创建工厂
+ *
+ * @param algorithm 算法
+ * @return DigesterFactory
+ */
+ public static DigesterFactory ofJdk(final String algorithm) {
+ return of(SecureUtil.createJdkMessageDigest(algorithm));
+ }
+
+ /**
+ * 创建工厂,使用{@link GlobalBouncyCastleProvider}找到的提供方。
+ *
+ * @param algorithm 算法
+ * @return DigesterFactory
+ */
+ public static DigesterFactory of(final String algorithm) {
+ return of(SecureUtil.createMessageDigest(algorithm));
+ }
+
+ /**
+ * 创建工厂
+ *
+ * @param messageDigest {@link MessageDigest},可以通过{@link SecureUtil#createMessageDigest(String)} 创建
+ * @return DigesterFactory
+ */
+ public static DigesterFactory of(final MessageDigest messageDigest) {
+ return new DigesterFactory(messageDigest);
+ }
+
+ private final MessageDigest prototype;
+ private final boolean cloneSupport;
+
+ /**
+ * 构造
+ *
+ * @param messageDigest {@link MessageDigest}模板
+ */
+ private DigesterFactory(final MessageDigest messageDigest) {
+ this.prototype = messageDigest;
+ this.cloneSupport = checkCloneSupport(messageDigest);
+ }
+
+ /**
+ * 创建{@link Digester}
+ *
+ * @return {@link Digester}
+ */
+ public Digester createDigester() {
+ return new Digester(createMessageDigester());
+ }
+
+ /**
+ * 创建{@link MessageDigest}
+ *
+ * @return {@link MessageDigest}
+ */
+ public MessageDigest createMessageDigester() {
+ if (cloneSupport) {
+ try {
+ return (MessageDigest) prototype.clone();
+ } catch (final CloneNotSupportedException ignore) {
+ // ignore
+ }
+ }
+ return SecureUtil.createJdkMessageDigest(prototype.getAlgorithm());
+ }
+
+ /**
+ * 检查{@link MessageDigest}对象是否支持clone方法
+ *
+ * @param messageDigest {@link MessageDigest}
+ * @return 是否支持clone方法
+ */
+ private static boolean checkCloneSupport(final MessageDigest messageDigest) {
+ try {
+ messageDigest.clone();
+ return true;
+ } catch (final CloneNotSupportedException e) {
+ return false;
+ }
+ }
+}
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/MD5.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/MD5.java
index 4f759538c..30d8f0d39 100644
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/MD5.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/MD5.java
@@ -6,33 +6,37 @@ import java.nio.charset.Charset;
/**
* MD5算法
- *
+ *
* @author looly
* @since 4.4.3
*/
public class MD5 extends Digester {
private static final long serialVersionUID = 1L;
-
+
+ // issue#I6ZIQH
+ // MD5算法不使用BC库,使用JDK默认以提高初始性能
+ private static final DigesterFactory FACTORY = DigesterFactory.ofJdk(DigestAlgorithm.MD5.getValue());
+
/**
* 创建MD5实例
- *
+ *
* @return MD5
* @since 4.6.0
*/
public static MD5 create() {
return new MD5();
}
-
+
/**
* 构造
*/
public MD5() {
- super(DigestAlgorithm.MD5);
+ super(FACTORY.createMessageDigester());
}
/**
* 构造
- *
+ *
* @param salt 盐值
*/
public MD5(byte[] salt) {
@@ -41,7 +45,7 @@ public class MD5 extends Digester {
/**
* 构造
- *
+ *
* @param salt 盐值
* @param digestCount 摘要次数,当此值小于等于1,默认为1。
*/
@@ -51,7 +55,7 @@ public class MD5 extends Digester {
/**
* 构造
- *
+ *
* @param salt 盐值
* @param saltPosition 加盐位置,即将盐值字符串放置在数据的index数,默认0
* @param digestCount 摘要次数,当此值小于等于1,默认为1。
@@ -62,10 +66,10 @@ public class MD5 extends Digester {
this.saltPosition = saltPosition;
this.digestCount = digestCount;
}
-
+
/**
* 生成16位MD5摘要
- *
+ *
* @param data 数据
* @param charset 编码
* @return 16位MD5摘要
@@ -77,7 +81,7 @@ public class MD5 extends Digester {
/**
* 生成16位MD5摘要
- *
+ *
* @param data 数据
* @return 16位MD5摘要
* @since 4.5.1
@@ -88,7 +92,7 @@ public class MD5 extends Digester {
/**
* 生成16位MD5摘要
- *
+ *
* @param data 数据
* @return 16位MD5摘要
* @since 4.5.1
@@ -99,7 +103,7 @@ public class MD5 extends Digester {
/**
* 生成16位MD5摘要
- *
+ *
* @param data 数据
* @return 16位MD5摘要
*/
@@ -109,7 +113,7 @@ public class MD5 extends Digester {
/**
* 生成16位MD5摘要
- *
+ *
* @param data 数据
* @return 16位MD5摘要
* @since 4.5.1
diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Md5Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Md5Test.java
index 2c7cdb407..0216105d3 100644
--- a/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Md5Test.java
+++ b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Md5Test.java
@@ -1,5 +1,7 @@
package cn.hutool.crypto.digest;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.thread.ConcurrencyTester;
import org.junit.Assert;
import org.junit.Test;
@@ -17,4 +19,15 @@ public class Md5Test {
Assert.assertEquals(16, hex16.length());
Assert.assertEquals("cb143acd6c929826", hex16);
}
+
+ @Test
+ public void md5ThreadSafeTest() {
+ final String text = "Hutool md5 test str";
+ final ConcurrencyTester tester = new ConcurrencyTester(1000);
+ tester.test(()->{
+ final String digest = new MD5().digestHex(text);
+ Assert.assertEquals("8060075dd8df47bac3247438e940a728", digest);
+ });
+ IoUtil.close(tester);
+ }
}