优化MD5性能

This commit is contained in:
Looly 2023-05-04 03:14:12 +08:00
parent 52135e9aeb
commit 049086aa79
6 changed files with 205 additions and 54 deletions

View File

@ -17,10 +17,11 @@
* 【http 】 修复HttpDownloader.downloadFile 方法缺少static问题issue#I6Z8VU@Gitee * 【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 * 【extra 】 JschUtil新增一个重载方法以支持私钥以byte数组形式载入pr#3057@Github
* 【crypto】 优化MD5性能issue#I6ZIQH@Gitee
### 🐞Bug修复 ### 🐞Bug修复
* 【core 】 修复CollUtil.reverseNew针对非可变列表异常issue#3056@Github * 【core 】 修复CollUtil.reverseNew针对非可变列表异常issue#3056@Github

View File

@ -1057,6 +1057,20 @@ public class SecureUtil {
return messageDigest; return messageDigest;
} }
/**
* 创建{@link MessageDigest}使用JDK默认的Provider<br>
*
* @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} * 创建{@link Mac}
* *

View File

@ -22,7 +22,7 @@ import java.security.Provider;
/** /**
* 摘要算法<br> * 摘要算法<br>
* 注意此对象实例化后为非线程安全 * 注意此对象实例化后为非线程安全
* *
* @author Looly * @author Looly
* *
*/ */
@ -40,7 +40,7 @@ public class Digester implements Serializable {
// ------------------------------------------------------------------------------------------- Constructor start // ------------------------------------------------------------------------------------------- Constructor start
/** /**
* 构造 * 构造
* *
* @param algorithm 算法枚举 * @param algorithm 算法枚举
*/ */
public Digester(DigestAlgorithm algorithm) { public Digester(DigestAlgorithm algorithm) {
@ -49,7 +49,7 @@ public class Digester implements Serializable {
/** /**
* 构造 * 构造
* *
* @param algorithm 算法枚举 * @param algorithm 算法枚举
*/ */
public Digester(String algorithm) { public Digester(String algorithm) {
@ -58,7 +58,7 @@ public class Digester implements Serializable {
/** /**
* 构造 * 构造
* *
* @param algorithm 算法 * @param algorithm 算法
* @param provider 算法提供者null表示JDK默认可以引入Bouncy Castle等来提供更多算法支持 * @param provider 算法提供者null表示JDK默认可以引入Bouncy Castle等来提供更多算法支持
* @since 4.5.1 * @since 4.5.1
@ -69,7 +69,7 @@ public class Digester implements Serializable {
/** /**
* 构造 * 构造
* *
* @param algorithm 算法 * @param algorithm 算法
* @param provider 算法提供者null表示JDK默认可以引入Bouncy Castle等来提供更多算法支持 * @param provider 算法提供者null表示JDK默认可以引入Bouncy Castle等来提供更多算法支持
* @since 4.5.1 * @since 4.5.1
@ -77,11 +77,20 @@ public class Digester implements Serializable {
public Digester(String algorithm, Provider provider) { public Digester(String algorithm, Provider provider) {
init(algorithm, provider); init(algorithm, provider);
} }
/**
* 构造
*
* @param messageDigest {@link MessageDigest}
*/
public Digester(final MessageDigest messageDigest) {
this.digest = messageDigest;
}
// ------------------------------------------------------------------------------------------- Constructor end // ------------------------------------------------------------------------------------------- Constructor end
/** /**
* 初始化 * 初始化
* *
* @param algorithm 算法 * @param algorithm 算法
* @param provider 算法提供者null表示JDK默认可以引入Bouncy Castle等来提供更多算法支持 * @param provider 算法提供者null表示JDK默认可以引入Bouncy Castle等来提供更多算法支持
* @return Digester * @return Digester
@ -99,10 +108,10 @@ public class Digester implements Serializable {
} }
return this; return this;
} }
/** /**
* 设置加盐内容 * 设置加盐内容
* *
* @param salt 盐值 * @param salt 盐值
* @return this * @return this
* @since 4.4.3 * @since 4.4.3
@ -115,18 +124,18 @@ public class Digester implements Serializable {
/** /**
* 设置加盐的位置只有盐值存在时有效<br> * 设置加盐的位置只有盐值存在时有效<br>
* 加盐的位置指盐位于数据byte数组中的位置例如 * 加盐的位置指盐位于数据byte数组中的位置例如
* *
* <pre> * <pre>
* data: 0123456 * data: 0123456
* </pre> * </pre>
* *
* 则当saltPosition = 2时盐位于data的1和2中间即第二个空隙 * 则当saltPosition = 2时盐位于data的1和2中间即第二个空隙
* *
* <pre> * <pre>
* data: 01[salt]23456 * data: 01[salt]23456
* </pre> * </pre>
* *
* *
* @param saltPosition 盐的位置 * @param saltPosition 盐的位置
* @return this * @return this
* @since 4.4.3 * @since 4.4.3
@ -138,7 +147,7 @@ public class Digester implements Serializable {
/** /**
* 设置重复计算摘要值次数 * 设置重复计算摘要值次数
* *
* @param digestCount 摘要值次数 * @param digestCount 摘要值次数
* @return this * @return this
*/ */
@ -149,7 +158,7 @@ public class Digester implements Serializable {
/** /**
* 重置{@link MessageDigest} * 重置{@link MessageDigest}
* *
* @return this * @return this
* @since 4.5.1 * @since 4.5.1
*/ */
@ -161,7 +170,7 @@ public class Digester implements Serializable {
// ------------------------------------------------------------------------------------------- Digest // ------------------------------------------------------------------------------------------- Digest
/** /**
* 生成文件摘要 * 生成文件摘要
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @param charsetName 编码 * @param charsetName 编码
* @return 摘要 * @return 摘要
@ -169,10 +178,10 @@ public class Digester implements Serializable {
public byte[] digest(String data, String charsetName) { public byte[] digest(String data, String charsetName) {
return digest(data, CharsetUtil.charset(charsetName)); return digest(data, CharsetUtil.charset(charsetName));
} }
/** /**
* 生成文件摘要 * 生成文件摘要
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @param charset 编码 * @param charset 编码
* @return 摘要 * @return 摘要
@ -184,7 +193,7 @@ public class Digester implements Serializable {
/** /**
* 生成文件摘要 * 生成文件摘要
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @return 摘要 * @return 摘要
*/ */
@ -194,7 +203,7 @@ public class Digester implements Serializable {
/** /**
* 生成文件摘要并转为16进制字符串 * 生成文件摘要并转为16进制字符串
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @param charsetName 编码 * @param charsetName 编码
* @return 摘要 * @return 摘要
@ -202,10 +211,10 @@ public class Digester implements Serializable {
public String digestHex(String data, String charsetName) { public String digestHex(String data, String charsetName) {
return digestHex(data, CharsetUtil.charset(charsetName)); return digestHex(data, CharsetUtil.charset(charsetName));
} }
/** /**
* 生成文件摘要并转为16进制字符串 * 生成文件摘要并转为16进制字符串
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @param charset 编码 * @param charset 编码
* @return 摘要 * @return 摘要
@ -217,7 +226,7 @@ public class Digester implements Serializable {
/** /**
* 生成文件摘要 * 生成文件摘要
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @return 摘要 * @return 摘要
*/ */
@ -228,7 +237,7 @@ public class Digester implements Serializable {
/** /**
* 生成文件摘要<br> * 生成文件摘要<br>
* 使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE} * 使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE}
* *
* @param file 被摘要文件 * @param file 被摘要文件
* @return 摘要bytes * @return 摘要bytes
* @throws CryptoException Cause by IOException * @throws CryptoException Cause by IOException
@ -246,7 +255,7 @@ public class Digester implements Serializable {
/** /**
* 生成文件摘要并转为16进制字符串<br> * 生成文件摘要并转为16进制字符串<br>
* 使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE} * 使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE}
* *
* @param file 被摘要文件 * @param file 被摘要文件
* @return 摘要 * @return 摘要
*/ */
@ -256,7 +265,7 @@ public class Digester implements Serializable {
/** /**
* 生成摘要考虑加盐和重复摘要次数 * 生成摘要考虑加盐和重复摘要次数
* *
* @param data 数据bytes * @param data 数据bytes
* @return 摘要bytes * @return 摘要bytes
*/ */
@ -284,7 +293,7 @@ public class Digester implements Serializable {
/** /**
* 生成摘要并转为16进制字符串<br> * 生成摘要并转为16进制字符串<br>
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @return 摘要 * @return 摘要
*/ */
@ -294,7 +303,7 @@ public class Digester implements Serializable {
/** /**
* 生成摘要使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE} * 生成摘要使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE}
* *
* @param data {@link InputStream} 数据流 * @param data {@link InputStream} 数据流
* @return 摘要bytes * @return 摘要bytes
*/ */
@ -305,7 +314,7 @@ public class Digester implements Serializable {
/** /**
* 生成摘要并转为16进制字符串<br> * 生成摘要并转为16进制字符串<br>
* 使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE} * 使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE}
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @return 摘要 * @return 摘要
*/ */
@ -315,7 +324,7 @@ public class Digester implements Serializable {
/** /**
* 生成摘要 * 生成摘要
* *
* @param data {@link InputStream} 数据流 * @param data {@link InputStream} 数据流
* @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值 * @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要bytes * @return 摘要bytes
@ -325,7 +334,7 @@ public class Digester implements Serializable {
if (bufferLength < 1) { if (bufferLength < 1) {
bufferLength = IoUtil.DEFAULT_BUFFER_SIZE; bufferLength = IoUtil.DEFAULT_BUFFER_SIZE;
} }
byte[] result; byte[] result;
try { try {
if (ArrayUtil.isEmpty(this.salt)) { if (ArrayUtil.isEmpty(this.salt)) {
@ -336,14 +345,14 @@ public class Digester implements Serializable {
} catch (IOException e) { } catch (IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} }
return resetAndRepeatDigest(result); return resetAndRepeatDigest(result);
} }
/** /**
* 生成摘要并转为16进制字符串<br> * 生成摘要并转为16进制字符串<br>
* 使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE} * 使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE}
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值 * @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要 * @return 摘要
@ -354,7 +363,7 @@ public class Digester implements Serializable {
/** /**
* 获得 {@link MessageDigest} * 获得 {@link MessageDigest}
* *
* @return {@link MessageDigest} * @return {@link MessageDigest}
*/ */
public MessageDigest getDigest() { public MessageDigest getDigest() {
@ -363,7 +372,7 @@ public class Digester implements Serializable {
/** /**
* 获取散列长度0表示不支持此方法 * 获取散列长度0表示不支持此方法
* *
* @return 散列长度0表示不支持此方法 * @return 散列长度0表示不支持此方法
* @since 4.5.0 * @since 4.5.0
*/ */
@ -374,7 +383,7 @@ public class Digester implements Serializable {
// -------------------------------------------------------------------------------- Private method start // -------------------------------------------------------------------------------- Private method start
/** /**
* 生成摘要 * 生成摘要
* *
* @param data {@link InputStream} 数据流 * @param data {@link InputStream} 数据流
* @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值 * @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要bytes * @return 摘要bytes
@ -391,7 +400,7 @@ public class Digester implements Serializable {
/** /**
* 生成摘要 * 生成摘要
* *
* @param data {@link InputStream} 数据流 * @param data {@link InputStream} 数据流
* @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值 * @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要bytes * @return 摘要bytes
@ -430,7 +439,7 @@ public class Digester implements Serializable {
/** /**
* 生成摘要 * 生成摘要
* *
* @param datas 数据bytes * @param datas 数据bytes
* @return 摘要bytes * @return 摘要bytes
* @since 4.4.3 * @since 4.4.3
@ -447,7 +456,7 @@ public class Digester implements Serializable {
/** /**
* 重复计算摘要取决于{@link #digestCount} <br> * 重复计算摘要取决于{@link #digestCount} <br>
* 每次计算摘要前都会重置{@link #digest} * 每次计算摘要前都会重置{@link #digest}
* *
* @param digestData 第一次摘要过的数据 * @param digestData 第一次摘要过的数据
* @return 摘要 * @return 摘要
*/ */

View File

@ -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}对象<br>
* 参考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;
}
}
}

View File

@ -6,33 +6,37 @@ import java.nio.charset.Charset;
/** /**
* MD5算法 * MD5算法
* *
* @author looly * @author looly
* @since 4.4.3 * @since 4.4.3
*/ */
public class MD5 extends Digester { public class MD5 extends Digester {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
// issue#I6ZIQH
// MD5算法不使用BC库使用JDK默认以提高初始性能
private static final DigesterFactory FACTORY = DigesterFactory.ofJdk(DigestAlgorithm.MD5.getValue());
/** /**
* 创建MD5实例 * 创建MD5实例
* *
* @return MD5 * @return MD5
* @since 4.6.0 * @since 4.6.0
*/ */
public static MD5 create() { public static MD5 create() {
return new MD5(); return new MD5();
} }
/** /**
* 构造 * 构造
*/ */
public MD5() { public MD5() {
super(DigestAlgorithm.MD5); super(FACTORY.createMessageDigester());
} }
/** /**
* 构造 * 构造
* *
* @param salt 盐值 * @param salt 盐值
*/ */
public MD5(byte[] salt) { public MD5(byte[] salt) {
@ -41,7 +45,7 @@ public class MD5 extends Digester {
/** /**
* 构造 * 构造
* *
* @param salt 盐值 * @param salt 盐值
* @param digestCount 摘要次数当此值小于等于1,默认为1 * @param digestCount 摘要次数当此值小于等于1,默认为1
*/ */
@ -51,7 +55,7 @@ public class MD5 extends Digester {
/** /**
* 构造 * 构造
* *
* @param salt 盐值 * @param salt 盐值
* @param saltPosition 加盐位置即将盐值字符串放置在数据的index数默认0 * @param saltPosition 加盐位置即将盐值字符串放置在数据的index数默认0
* @param digestCount 摘要次数当此值小于等于1,默认为1 * @param digestCount 摘要次数当此值小于等于1,默认为1
@ -62,10 +66,10 @@ public class MD5 extends Digester {
this.saltPosition = saltPosition; this.saltPosition = saltPosition;
this.digestCount = digestCount; this.digestCount = digestCount;
} }
/** /**
* 生成16位MD5摘要 * 生成16位MD5摘要
* *
* @param data 数据 * @param data 数据
* @param charset 编码 * @param charset 编码
* @return 16位MD5摘要 * @return 16位MD5摘要
@ -77,7 +81,7 @@ public class MD5 extends Digester {
/** /**
* 生成16位MD5摘要 * 生成16位MD5摘要
* *
* @param data 数据 * @param data 数据
* @return 16位MD5摘要 * @return 16位MD5摘要
* @since 4.5.1 * @since 4.5.1
@ -88,7 +92,7 @@ public class MD5 extends Digester {
/** /**
* 生成16位MD5摘要 * 生成16位MD5摘要
* *
* @param data 数据 * @param data 数据
* @return 16位MD5摘要 * @return 16位MD5摘要
* @since 4.5.1 * @since 4.5.1
@ -99,7 +103,7 @@ public class MD5 extends Digester {
/** /**
* 生成16位MD5摘要 * 生成16位MD5摘要
* *
* @param data 数据 * @param data 数据
* @return 16位MD5摘要 * @return 16位MD5摘要
*/ */
@ -109,7 +113,7 @@ public class MD5 extends Digester {
/** /**
* 生成16位MD5摘要 * 生成16位MD5摘要
* *
* @param data 数据 * @param data 数据
* @return 16位MD5摘要 * @return 16位MD5摘要
* @since 4.5.1 * @since 4.5.1

View File

@ -1,5 +1,7 @@
package cn.hutool.crypto.digest; package cn.hutool.crypto.digest;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.thread.ConcurrencyTester;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -17,4 +19,15 @@ public class Md5Test {
Assert.assertEquals(16, hex16.length()); Assert.assertEquals(16, hex16.length());
Assert.assertEquals("cb143acd6c929826", hex16); 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);
}
} }