diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f0b81aa3..1bbb758cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@
# 🚀Changelog
-------------------------------------------------------------------------------------------------------------
-# 5.8.0.M2 (2022-04-01)
+# 5.8.0.M2 (2022-04-02)
### ❌不兼容特性
* 【extra 】 【可能兼容问题】BeanCopierCache的key结构变更
@@ -17,6 +17,7 @@
* 【crypto 】 HmacAlgorithm增加SM4CMAC(issue#2206@Github)
* 【http 】 增加HttpConfig,响应支持拦截(issue#2217@Github)
* 【core 】 增加BlockPolicy,ThreadUtil增加newFixedExecutor方法(pr#2231@Github)
+* 【crypto 】 BCMacEngine、Mac、CBCBlockCipherMacEngine、SM4MacEngine(issue#2206@Github)
### 🐞Bug修复
* 【core 】 IdcardUtil#getCityCodeByIdCard位数问题(issue#2224@Github)
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java
index 7625b9444..4fd9ee551 100644
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java
@@ -1,23 +1,11 @@
package cn.hutool.crypto.digest;
-import cn.hutool.core.codec.Base64;
-import cn.hutool.core.io.FileUtil;
-import cn.hutool.core.io.IoUtil;
-import cn.hutool.core.util.CharsetUtil;
-import cn.hutool.core.util.HexUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.crypto.CryptoException;
+import cn.hutool.crypto.digest.mac.Mac;
import cn.hutool.crypto.digest.mac.MacEngine;
import cn.hutool.crypto.digest.mac.MacEngineFactory;
import javax.crypto.spec.SecretKeySpec;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.InputStream;
-import java.io.Serializable;
-import java.nio.charset.Charset;
import java.security.Key;
-import java.security.MessageDigest;
import java.security.spec.AlgorithmParameterSpec;
/**
@@ -30,11 +18,9 @@ import java.security.spec.AlgorithmParameterSpec;
*
* @author Looly
*/
-public class HMac implements Serializable {
+public class HMac extends Mac {
private static final long serialVersionUID = 1L;
- private final MacEngine engine;
-
// ------------------------------------------------------------------------------------------- Constructor start
/**
@@ -93,7 +79,7 @@ public class HMac implements Serializable {
*
* @param algorithm 算法
* @param key 密钥
- * @param spec {@link AlgorithmParameterSpec}
+ * @param spec {@link AlgorithmParameterSpec}
* @since 5.6.12
*/
public HMac(String algorithm, Key key, AlgorithmParameterSpec spec) {
@@ -107,211 +93,7 @@ public class HMac implements Serializable {
* @since 4.5.13
*/
public HMac(MacEngine engine) {
- this.engine = engine;
+ super(engine);
}
// ------------------------------------------------------------------------------------------- Constructor end
-
- /**
- * 获得MAC算法引擎
- *
- * @return MAC算法引擎
- */
- public MacEngine getEngine() {
- return this.engine;
- }
-
- // ------------------------------------------------------------------------------------------- Digest
-
- /**
- * 生成文件摘要
- *
- * @param data 被摘要数据
- * @param charset 编码
- * @return 摘要
- */
- public byte[] digest(String data, Charset charset) {
- return digest(StrUtil.bytes(data, charset));
- }
-
- /**
- * 生成文件摘要
- *
- * @param data 被摘要数据
- * @return 摘要
- */
- public byte[] digest(String data) {
- return digest(data, CharsetUtil.CHARSET_UTF_8);
- }
-
- /**
- * 生成文件摘要,并转为Base64
- *
- * @param data 被摘要数据
- * @param isUrlSafe 是否使用URL安全字符
- * @return 摘要
- */
- public String digestBase64(String data, boolean isUrlSafe) {
- return digestBase64(data, CharsetUtil.CHARSET_UTF_8, isUrlSafe);
- }
-
- /**
- * 生成文件摘要,并转为Base64
- *
- * @param data 被摘要数据
- * @param charset 编码
- * @param isUrlSafe 是否使用URL安全字符
- * @return 摘要
- */
- public String digestBase64(String data, Charset charset, boolean isUrlSafe) {
- final byte[] digest = digest(data, charset);
- return isUrlSafe ? Base64.encodeUrlSafe(digest) : Base64.encode(digest);
- }
-
- /**
- * 生成文件摘要,并转为16进制字符串
- *
- * @param data 被摘要数据
- * @param charset 编码
- * @return 摘要
- */
- public String digestHex(String data, Charset charset) {
- return HexUtil.encodeHexStr(digest(data, charset));
- }
-
- /**
- * 生成文件摘要
- *
- * @param data 被摘要数据
- * @return 摘要
- */
- public String digestHex(String data) {
- return digestHex(data, CharsetUtil.CHARSET_UTF_8);
- }
-
- /**
- * 生成文件摘要
- * 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
- *
- * @param file 被摘要文件
- * @return 摘要bytes
- * @throws CryptoException Cause by IOException
- */
- public byte[] digest(File file) throws CryptoException {
- InputStream in = null;
- try {
- in = FileUtil.getInputStream(file);
- return digest(in);
- } finally {
- IoUtil.close(in);
- }
- }
-
- /**
- * 生成文件摘要,并转为16进制字符串
- * 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
- *
- * @param file 被摘要文件
- * @return 摘要
- */
- public String digestHex(File file) {
- return HexUtil.encodeHexStr(digest(file));
- }
-
- /**
- * 生成摘要
- *
- * @param data 数据bytes
- * @return 摘要bytes
- */
- public byte[] digest(byte[] data) {
- return digest(new ByteArrayInputStream(data), -1);
- }
-
- /**
- * 生成摘要,并转为16进制字符串
- *
- * @param data 被摘要数据
- * @return 摘要
- */
- public String digestHex(byte[] data) {
- return HexUtil.encodeHexStr(digest(data));
- }
-
- /**
- * 生成摘要,使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
- *
- * @param data {@link InputStream} 数据流
- * @return 摘要bytes
- */
- public byte[] digest(InputStream data) {
- return digest(data, IoUtil.DEFAULT_BUFFER_SIZE);
- }
-
- /**
- * 生成摘要,并转为16进制字符串
- * 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
- *
- * @param data 被摘要数据
- * @return 摘要
- */
- public String digestHex(InputStream data) {
- return HexUtil.encodeHexStr(digest(data));
- }
-
- /**
- * 生成摘要
- *
- * @param data {@link InputStream} 数据流
- * @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
- * @return 摘要bytes
- */
- public byte[] digest(InputStream data, int bufferLength) {
- return this.engine.digest(data, bufferLength);
- }
-
- /**
- * 生成摘要,并转为16进制字符串
- * 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
- *
- * @param data 被摘要数据
- * @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
- * @return 摘要
- */
- public String digestHex(InputStream data, int bufferLength) {
- return HexUtil.encodeHexStr(digest(data, bufferLength));
- }
-
- /**
- * 验证生成的摘要与给定的摘要比较是否一致
- * 简单比较每个byte位是否相同
- *
- * @param digest 生成的摘要
- * @param digestToCompare 需要比较的摘要
- * @return 是否一致
- * @see MessageDigest#isEqual(byte[], byte[])
- * @since 5.6.8
- */
- public boolean verify(byte[] digest, byte[] digestToCompare) {
- return MessageDigest.isEqual(digest, digestToCompare);
- }
-
- /**
- * 获取MAC算法块长度
- *
- * @return MAC算法块长度
- * @since 5.3.3
- */
- public int getMacLength() {
- return this.engine.getMacLength();
- }
-
- /**
- * 获取算法
- *
- * @return 算法
- * @since 5.3.3
- */
- public String getAlgorithm() {
- return this.engine.getAlgorithm();
- }
}
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java
index b734dd86d..0fcbb9bd1 100644
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java
@@ -14,9 +14,7 @@ import org.bouncycastle.crypto.params.ParametersWithIV;
* @author Looly
* @since 4.5.13
*/
-public class BCHMacEngine implements MacEngine {
-
- private Mac mac;
+public class BCHMacEngine extends BCMacEngine {
// ------------------------------------------------------------------------------------------- Constructor start
@@ -51,7 +49,18 @@ public class BCHMacEngine implements MacEngine {
* @since 4.5.13
*/
public BCHMacEngine(Digest digest, CipherParameters params) {
- init(digest, params);
+ this(new HMac(digest), params);
+ }
+
+ /**
+ * 构造
+ *
+ * @param mac {@link HMac}
+ * @param params 参数,例如密钥可以用{@link KeyParameter}
+ * @since 5.8.0
+ */
+ public BCHMacEngine(HMac mac, CipherParameters params) {
+ super(mac, params);
}
// ------------------------------------------------------------------------------------------- Constructor end
@@ -61,46 +70,9 @@ public class BCHMacEngine implements MacEngine {
* @param digest 摘要算法
* @param params 参数,例如密钥可以用{@link KeyParameter}
* @return this
+ * @see #init(Mac, CipherParameters)
*/
public BCHMacEngine init(Digest digest, CipherParameters params) {
- mac = new HMac(digest);
- mac.init(params);
- return this;
- }
-
- /**
- * 获得 {@link Mac}
- *
- * @return {@link Mac}
- */
- public Mac getMac() {
- return mac;
- }
-
- @Override
- public void update(byte[] in, int inOff, int len) {
- this.mac.update(in, inOff, len);
- }
-
- @Override
- public byte[] doFinal() {
- final byte[] result = new byte[getMacLength()];
- this.mac.doFinal(result, 0);
- return result;
- }
-
- @Override
- public void reset() {
- this.mac.reset();
- }
-
- @Override
- public int getMacLength() {
- return mac.getMacSize();
- }
-
- @Override
- public String getAlgorithm() {
- return this.mac.getAlgorithmName();
+ return (BCHMacEngine) init(new HMac(digest), params);
}
}
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCMacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCMacEngine.java
new file mode 100644
index 000000000..dc2b3e4ca
--- /dev/null
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCMacEngine.java
@@ -0,0 +1,80 @@
+package cn.hutool.crypto.digest.mac;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+/**
+ * BouncyCastle的MAC算法实现引擎,使用{@link Mac} 实现摘要
+ * 当引入BouncyCastle库时自动使用其作为Provider
+ *
+ * @author Looly
+ * @since 5.8.0
+ */
+public class BCMacEngine implements MacEngine {
+
+ private Mac mac;
+
+ // ------------------------------------------------------------------------------------------- Constructor start
+ /**
+ * 构造
+ *
+ * @param mac {@link Mac}
+ * @param params 参数,例如密钥可以用{@link KeyParameter}
+ * @since 5.8.0
+ */
+ public BCMacEngine(Mac mac, CipherParameters params) {
+ init(mac, params);
+ }
+ // ------------------------------------------------------------------------------------------- Constructor end
+
+ /**
+ * 初始化
+ *
+ * @param mac 摘要算法
+ * @param params 参数,例如密钥可以用{@link KeyParameter}
+ * @return this
+ * @since 5.8.0
+ */
+ public BCMacEngine init(Mac mac, CipherParameters params) {
+ mac.init(params);
+ this.mac = mac;
+ return this;
+ }
+
+ /**
+ * 获得 {@link Mac}
+ *
+ * @return {@link Mac}
+ */
+ public Mac getMac() {
+ return mac;
+ }
+
+ @Override
+ public void update(byte[] in, int inOff, int len) {
+ this.mac.update(in, inOff, len);
+ }
+
+ @Override
+ public byte[] doFinal() {
+ final byte[] result = new byte[getMacLength()];
+ this.mac.doFinal(result, 0);
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ this.mac.reset();
+ }
+
+ @Override
+ public int getMacLength() {
+ return mac.getMacSize();
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return this.mac.getAlgorithmName();
+ }
+}
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/CBCBlockCipherMacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/CBCBlockCipherMacEngine.java
new file mode 100644
index 000000000..b3ec79bc0
--- /dev/null
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/CBCBlockCipherMacEngine.java
@@ -0,0 +1,100 @@
+package cn.hutool.crypto.digest.mac;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+import java.security.Key;
+
+/**
+ * {@link CBCBlockCipherMac}实现的MAC算法,使用CBC Block方式
+ *
+ * @author looly
+ * @since 5.8.0
+ */
+public class CBCBlockCipherMacEngine extends BCMacEngine {
+
+ /**
+ * 构造
+ *
+ * @param digest 摘要算法,为{@link Digest} 的接口实现
+ * @param macSizeInBits mac结果的bits长度,必须为8的倍数
+ * @param key 密钥
+ * @param iv 加盐
+ * @since 5.7.12
+ */
+ public CBCBlockCipherMacEngine(BlockCipher digest, int macSizeInBits, Key key, byte[] iv) {
+ this(digest, macSizeInBits, key.getEncoded(), iv);
+ }
+
+ /**
+ * 构造
+ *
+ * @param digest 摘要算法,为{@link Digest} 的接口实现
+ * @param macSizeInBits mac结果的bits长度,必须为8的倍数
+ * @param key 密钥
+ * @param iv 加盐
+ */
+ public CBCBlockCipherMacEngine(BlockCipher digest, int macSizeInBits, byte[] key, byte[] iv) {
+ this(digest, macSizeInBits, new ParametersWithIV(new KeyParameter(key), iv));
+ }
+
+ /**
+ * 构造
+ *
+ * @param cipher 算法,为{@link BlockCipher} 的接口实现
+ * @param macSizeInBits mac结果的bits长度,必须为8的倍数
+ * @param key 密钥
+ */
+ public CBCBlockCipherMacEngine(BlockCipher cipher, int macSizeInBits, Key key) {
+ this(cipher, macSizeInBits, key.getEncoded());
+ }
+
+ /**
+ * 构造
+ *
+ * @param cipher 算法,为{@link BlockCipher} 的接口实现
+ * @param macSizeInBits mac结果的bits长度,必须为8的倍数
+ * @param key 密钥
+ */
+ public CBCBlockCipherMacEngine(BlockCipher cipher, int macSizeInBits, byte[] key) {
+ this(cipher, macSizeInBits, new KeyParameter(key));
+ }
+
+ /**
+ * 构造
+ *
+ * @param cipher 算法,为{@link BlockCipher} 的接口实现
+ * @param macSizeInBits mac结果的bits长度,必须为8的倍数
+ * @param params 参数,例如密钥可以用{@link KeyParameter}
+ */
+ public CBCBlockCipherMacEngine(BlockCipher cipher, int macSizeInBits, CipherParameters params) {
+ this(new CBCBlockCipherMac(cipher, macSizeInBits), params);
+ }
+
+ /**
+ * 构造
+ *
+ * @param mac {@link CBCBlockCipherMac}
+ * @param params 参数,例如密钥可以用{@link KeyParameter}
+ */
+ public CBCBlockCipherMacEngine(CBCBlockCipherMac mac, CipherParameters params) {
+ super(mac, params);
+ }
+
+ /**
+ * 初始化
+ *
+ * @param cipher {@link BlockCipher}
+ * @param params 参数,例如密钥可以用{@link KeyParameter}
+ * @return this
+ * @see #init(Mac, CipherParameters)
+ */
+ public CBCBlockCipherMacEngine init(BlockCipher cipher, CipherParameters params) {
+ return (CBCBlockCipherMacEngine) init(new CBCBlockCipherMac(cipher), params);
+ }
+}
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/Mac.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/Mac.java
new file mode 100644
index 000000000..5ebe191ab
--- /dev/null
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/Mac.java
@@ -0,0 +1,246 @@
+package cn.hutool.crypto.digest.mac;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.CryptoException;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+
+/**
+ * MAC摘要算法(此类兼容和JCE的 {@code javax.crypto.Mac}对象和BC库的{@code org.bouncycastle.crypto.Mac}对象)
+ * MAC,全称为“Message Authentication Code”,中文名“消息鉴别码”
+ * 主要是利用指定算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。
+ * 一般的,消息鉴别码用于验证传输于两个共同享有一个密钥的单位之间的消息。
+ * 注意:此对象实例化后为非线程安全!
+ *
+ * @author Looly
+ * @since 5.8.0
+ */
+public class Mac implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final MacEngine engine;
+
+ /**
+ * 构造
+ *
+ * @param engine MAC算法实现引擎
+ */
+ public Mac(MacEngine engine) {
+ this.engine = engine;
+ }
+ // ------------------------------------------------------------------------------------------- Constructor end
+
+ /**
+ * 获得MAC算法引擎
+ *
+ * @return MAC算法引擎
+ */
+ public MacEngine getEngine() {
+ return this.engine;
+ }
+
+ // ------------------------------------------------------------------------------------------- Digest
+
+ /**
+ * 生成文件摘要
+ *
+ * @param data 被摘要数据
+ * @param charset 编码
+ * @return 摘要
+ */
+ public byte[] digest(String data, Charset charset) {
+ return digest(StrUtil.bytes(data, charset));
+ }
+
+ /**
+ * 生成文件摘要
+ *
+ * @param data 被摘要数据
+ * @return 摘要
+ */
+ public byte[] digest(String data) {
+ return digest(data, CharsetUtil.CHARSET_UTF_8);
+ }
+
+ /**
+ * 生成文件摘要,并转为Base64
+ *
+ * @param data 被摘要数据
+ * @param isUrlSafe 是否使用URL安全字符
+ * @return 摘要
+ */
+ public String digestBase64(String data, boolean isUrlSafe) {
+ return digestBase64(data, CharsetUtil.CHARSET_UTF_8, isUrlSafe);
+ }
+
+ /**
+ * 生成文件摘要,并转为Base64
+ *
+ * @param data 被摘要数据
+ * @param charset 编码
+ * @param isUrlSafe 是否使用URL安全字符
+ * @return 摘要
+ */
+ public String digestBase64(String data, Charset charset, boolean isUrlSafe) {
+ final byte[] digest = digest(data, charset);
+ return isUrlSafe ? Base64.encodeUrlSafe(digest) : Base64.encode(digest);
+ }
+
+ /**
+ * 生成文件摘要,并转为16进制字符串
+ *
+ * @param data 被摘要数据
+ * @param charset 编码
+ * @return 摘要
+ */
+ public String digestHex(String data, Charset charset) {
+ return HexUtil.encodeHexStr(digest(data, charset));
+ }
+
+ /**
+ * 生成文件摘要
+ *
+ * @param data 被摘要数据
+ * @return 摘要
+ */
+ public String digestHex(String data) {
+ return digestHex(data, CharsetUtil.CHARSET_UTF_8);
+ }
+
+ /**
+ * 生成文件摘要
+ * 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
+ *
+ * @param file 被摘要文件
+ * @return 摘要bytes
+ * @throws CryptoException Cause by IOException
+ */
+ public byte[] digest(File file) throws CryptoException {
+ InputStream in = null;
+ try {
+ in = FileUtil.getInputStream(file);
+ return digest(in);
+ } finally {
+ IoUtil.close(in);
+ }
+ }
+
+ /**
+ * 生成文件摘要,并转为16进制字符串
+ * 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
+ *
+ * @param file 被摘要文件
+ * @return 摘要
+ */
+ public String digestHex(File file) {
+ return HexUtil.encodeHexStr(digest(file));
+ }
+
+ /**
+ * 生成摘要
+ *
+ * @param data 数据bytes
+ * @return 摘要bytes
+ */
+ public byte[] digest(byte[] data) {
+ return digest(new ByteArrayInputStream(data), -1);
+ }
+
+ /**
+ * 生成摘要,并转为16进制字符串
+ *
+ * @param data 被摘要数据
+ * @return 摘要
+ */
+ public String digestHex(byte[] data) {
+ return HexUtil.encodeHexStr(digest(data));
+ }
+
+ /**
+ * 生成摘要,使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
+ *
+ * @param data {@link InputStream} 数据流
+ * @return 摘要bytes
+ */
+ public byte[] digest(InputStream data) {
+ return digest(data, IoUtil.DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * 生成摘要,并转为16进制字符串
+ * 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
+ *
+ * @param data 被摘要数据
+ * @return 摘要
+ */
+ public String digestHex(InputStream data) {
+ return HexUtil.encodeHexStr(digest(data));
+ }
+
+ /**
+ * 生成摘要
+ *
+ * @param data {@link InputStream} 数据流
+ * @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
+ * @return 摘要bytes
+ */
+ public byte[] digest(InputStream data, int bufferLength) {
+ return this.engine.digest(data, bufferLength);
+ }
+
+ /**
+ * 生成摘要,并转为16进制字符串
+ * 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
+ *
+ * @param data 被摘要数据
+ * @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
+ * @return 摘要
+ */
+ public String digestHex(InputStream data, int bufferLength) {
+ return HexUtil.encodeHexStr(digest(data, bufferLength));
+ }
+
+ /**
+ * 验证生成的摘要与给定的摘要比较是否一致
+ * 简单比较每个byte位是否相同
+ *
+ * @param digest 生成的摘要
+ * @param digestToCompare 需要比较的摘要
+ * @return 是否一致
+ * @see MessageDigest#isEqual(byte[], byte[])
+ * @since 5.6.8
+ */
+ public boolean verify(byte[] digest, byte[] digestToCompare) {
+ return MessageDigest.isEqual(digest, digestToCompare);
+ }
+
+ /**
+ * 获取MAC算法块长度
+ *
+ * @return MAC算法块长度
+ * @since 5.3.3
+ */
+ public int getMacLength() {
+ return this.engine.getMacLength();
+ }
+
+ /**
+ * 获取算法
+ *
+ * @return 算法
+ * @since 5.3.3
+ */
+ public String getAlgorithm() {
+ return this.engine.getAlgorithm();
+ }
+}
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/SM4MacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/SM4MacEngine.java
new file mode 100644
index 000000000..58ab867fc
--- /dev/null
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/SM4MacEngine.java
@@ -0,0 +1,24 @@
+package cn.hutool.crypto.digest.mac;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.engines.SM4Engine;
+
+/**
+ * SM4算法的MAC引擎实现
+ *
+ * @author looly
+ * @since 5.8.0
+ */
+public class SM4MacEngine extends CBCBlockCipherMacEngine {
+
+ private static final int MAC_SIZE = 128;
+
+ /**
+ * 构造
+ *
+ * @param params {@link CipherParameters}
+ */
+ public SM4MacEngine(CipherParameters params) {
+ super(new SM4Engine(), MAC_SIZE, params);
+ }
+}
diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/CBCBlockCipherMacEngineTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/CBCBlockCipherMacEngineTest.java
new file mode 100644
index 000000000..8e4bdb6e3
--- /dev/null
+++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/CBCBlockCipherMacEngineTest.java
@@ -0,0 +1,43 @@
+package cn.hutool.crypto.test.digest;
+
+import cn.hutool.crypto.KeyUtil;
+import cn.hutool.crypto.digest.mac.Mac;
+import cn.hutool.crypto.digest.mac.SM4MacEngine;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CBCBlockCipherMacEngineTest {
+
+ @Test
+ public void SM4CMACTest(){
+ // https://github.com/dromara/hutool/issues/2206
+ final byte[] key = new byte[16];
+ final CipherParameters parameter = new KeyParameter(KeyUtil.generateKey("SM4", key).getEncoded());
+ Mac mac = new Mac(new SM4MacEngine(parameter));
+
+ // 原文
+ String testStr = "test中文";
+
+ String macHex1 = mac.digestHex(testStr);
+ Assert.assertEquals("3212e848db7f816a4bd591ad9948debf", macHex1);
+ }
+
+ @Test
+ public void SM4CMACWithIVTest(){
+ // https://github.com/dromara/hutool/issues/2206
+ final byte[] key = new byte[16];
+ final byte[] iv = new byte[16];
+ CipherParameters parameter = new KeyParameter(KeyUtil.generateKey("SM4", key).getEncoded());
+ parameter = new ParametersWithIV(parameter, iv);
+ Mac mac = new Mac(new SM4MacEngine(parameter));
+
+ // 原文
+ String testStr = "test中文";
+
+ String macHex1 = mac.digestHex(testStr);
+ Assert.assertEquals("3212e848db7f816a4bd591ad9948debf", macHex1);
+ }
+}