From 049086aa791fcd9f5630c65a7b1ee0b0d622da36 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 4 May 2023 03:14:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96MD5=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +- .../java/cn/hutool/crypto/SecureUtil.java | 14 +++ .../cn/hutool/crypto/digest/Digester.java | 87 +++++++------- .../hutool/crypto/digest/DigesterFactory.java | 110 ++++++++++++++++++ .../java/cn/hutool/crypto/digest/MD5.java | 32 ++--- .../java/cn/hutool/crypto/digest/Md5Test.java | 13 +++ 6 files changed, 205 insertions(+), 54 deletions(-) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigesterFactory.java 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); + } }