Browse Source

add ZeroPadding support

Looly 6 years ago
parent
commit
c9f77e1746

+ 2 - 0
CHANGELOG.md

@@ -16,6 +16,8 @@
 * 【cron】        添加获取任务表的方法(issue#I12E5H@Gitee)
 * 【http】        SoapClient增加reset方法用于此对象的复用(issue#I12CCC@Gitee)
 * 【db】          StatementUtil增加setParam方法
+* 【db】          Entity.fieldList改为有序实现
+* 【crypto】      AES、DES增加对ZeroPadding的支持(issue#551@Github)
 
 ### Bug修复
 * 【core】        修复DateUtil.offset导致的时区错误问题(issue#I1294O@Gitee)

+ 57 - 9
hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java

@@ -1,15 +1,15 @@
 package cn.hutool.core.util;
 
-import java.lang.reflect.Array;
-import java.nio.ByteBuffer;
-import java.util.*;
-
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.collection.IterUtil;
 import cn.hutool.core.exceptions.UtilException;
 import cn.hutool.core.lang.Editor;
 import cn.hutool.core.lang.Filter;
 
+import java.lang.reflect.Array;
+import java.nio.ByteBuffer;
+import java.util.*;
+
 /**
  * 数组工具类
  * 
@@ -494,15 +494,63 @@ public class ArrayUtil {
 	 * 调整大小后拷贝原数组到新数组下。扩大则占位前N个位置,缩小则截断
 	 * 
 	 * @param <T> 数组元素类型
-	 * @param buffer 原数组
+	 * @param data 原数组
 	 * @param newSize 新的数组大小
 	 * @param componentType 数组元素类型
 	 * @return 调整后的新数组
 	 */
-	public static <T> T[] resize(T[] buffer, int newSize, Class<?> componentType) {
-		T[] newArray = newArray(componentType, newSize);
-		if (isNotEmpty(buffer)) {
-			System.arraycopy(buffer, 0, newArray, 0, Math.min(buffer.length, newSize));
+	public static <T> T[] resize(T[] data, int newSize, Class<?> componentType) {
+		if(newSize < 0){
+			return data;
+		}
+
+		final T[] newArray = newArray(componentType, newSize);
+		if (newSize > 0 && isNotEmpty(data)) {
+			System.arraycopy(data, 0, newArray, 0, Math.min(data.length, newSize));
+		}
+		return newArray;
+	}
+
+	/**
+	 * 生成一个新的重新设置大小的数组<br>
+	 * 调整大小后拷贝原数组到新数组下。扩大则占位前N个位置,其它位置补充0,缩小则截断
+	 *
+	 * @param array 原数组
+	 * @param newSize 新的数组大小
+	 * @return 调整后的新数组
+	 * @since 4.6.7
+	 */
+	public static Object resize(Object array, int newSize) {
+		if(newSize < 0){
+			return array;
+		}
+		if (null == array) {
+			return null;
+		}
+		final int length = length(array);
+		final Object newArray = Array.newInstance(array.getClass().getComponentType(), newSize);
+		if (newSize > 0 && isNotEmpty(array)) {
+			System.arraycopy(array, 0, newArray, 0, Math.min(length, newSize));
+		}
+		return newArray;
+	}
+
+	/**
+	 * 生成一个新的重新设置大小的数组<br>
+	 * 调整大小后拷贝原数组到新数组下。扩大则占位前N个位置,其它位置补充0,缩小则截断
+	 *
+	 * @param bytes 原数组
+	 * @param newSize 新的数组大小
+	 * @return 调整后的新数组
+	 * @since 4.6.7
+	 */
+	public static byte[] resize(byte[] bytes, int newSize) {
+		if(newSize < 0){
+			return bytes;
+		}
+		final byte[] newArray = new byte[newSize];
+		if (newSize > 0 && isNotEmpty(bytes)) {
+			System.arraycopy(bytes, 0, newArray, 0, Math.min(bytes.length, newSize));
 		}
 		return newArray;
 	}

+ 5 - 5
hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java

@@ -337,7 +337,7 @@ public class RandomUtil {
 	 * @return 随机元素
 	 */
 	public static <T> List<T> randomEles(List<T> list, int count) {
-		final List<T> result = new ArrayList<T>(count);
+		final List<T> result = new ArrayList<>(count);
 		int limit = list.size();
 		while (result.size() < count) {
 			result.add(randomEle(list, limit));
@@ -361,7 +361,7 @@ public class RandomUtil {
 			throw new IllegalArgumentException("Count is larger than collection distinct size !");
 		}
 
-		final HashSet<T> result = new HashSet<T>(count);
+		final HashSet<T> result = new HashSet<>(count);
 		int limit = source.size();
 		while (result.size() < count) {
 			result.add(randomEle(source, limit));
@@ -409,14 +409,14 @@ public class RandomUtil {
 	 * @return 随机字符串
 	 */
 	public static String randomString(String baseString, int length) {
-		final StringBuilder sb = new StringBuilder();
+		final StringBuilder sb = new StringBuilder(length);
 
 		if (length < 1) {
 			length = 1;
 		}
 		int baseLength = baseString.length();
 		for (int i = 0; i < length; i++) {
-			int number = getRandom().nextInt(baseLength);
+			int number = randomInt(baseLength);
 			sb.append(baseString.charAt(number));
 		}
 		return sb.toString();
@@ -450,7 +450,7 @@ public class RandomUtil {
 	 * @since 3.1.2
 	 */
 	public static char randomChar(String baseString) {
-		return baseString.charAt(getRandom().nextInt(baseString.length()));
+		return baseString.charAt(randomInt(baseString.length()));
 	}
 
 	/**

+ 34 - 14
hutool-crypto/src/main/java/cn/hutool/crypto/Mode.java

@@ -2,25 +2,45 @@ package cn.hutool.crypto;
 
 /**
  * 模式
+ *
+ * <p>
+ * 加密算法模式,是用来描述加密算法(此处特指分组密码,不包括流密码,)在加密时对明文分组的模式,它代表了不同的分组方式
+ *
  * @author Looly
  * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher"> Cipher章节</a>
  * @since 3.0.8
  */
-public enum Mode{
-	/** 无模式 */
-	NONE, 
-	/** 密码分组连接模式(Cipher Block Chaining) */
-	CBC, 
-	/** 密文反馈模式(Cipher Feedback) */
-	CFB, 
-	/** 计数器模式(A simplification of OFB) */
+public enum Mode {
+	/**
+	 * 无模式
+	 */
+	NONE,
+	/**
+	 * 密码分组连接模式(Cipher Block Chaining)
+	 */
+	CBC,
+	/**
+	 * 密文反馈模式(Cipher Feedback)
+	 */
+	CFB,
+	/**
+	 * 计数器模式(A simplification of OFB)
+	 */
 	CTR,
-	/** Cipher Text Stealing */
+	/**
+	 * Cipher Text Stealing
+	 */
 	CTS,
-	/** 电子密码本模式(Electronic CodeBook) */
-	ECB, 
-	/** 输出反馈模式(Output Feedback) */
-	OFB, 
-	/** Propagating Cipher Block */
+	/**
+	 * 电子密码本模式(Electronic CodeBook)
+	 */
+	ECB,
+	/**
+	 * 输出反馈模式(Output Feedback)
+	 */
+	OFB,
+	/**
+	 * Propagating Cipher Block
+	 */
 	PCBC;
 }

+ 13 - 4
hutool-crypto/src/main/java/cn/hutool/crypto/Padding.java

@@ -2,17 +2,26 @@ package cn.hutool.crypto;
 
 /**
  * 补码方式
- * 
+ *
+ * <p>
+ * 补码方式是在分组密码中,当明文长度不是分组长度的整数倍时,需要在最后一个分组中填充一些数据使其凑满一个分组的长度。
+ *
  * @author Looly
  * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher"> Cipher章节</a>
  * @since 3.0.8
  */
 public enum Padding {
-	/** 无补码 */
-	NoPadding, 
+	/**
+	 * 无补码
+	 */
+	NoPadding,
+	/**
+	 * 0补码,既不满block长度时使用0填充
+	 */
+	ZeroPadding,
 	ISO10126Padding,
 	OAEPPadding,
 	PKCS1Padding,
-	PKCS5Padding, 
+	PKCS5Padding,
 	SSL3Padding
 }

+ 70 - 42
hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java

@@ -3,6 +3,7 @@ package cn.hutool.crypto.symmetric;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.IvParameterSpec;
 
+import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.Mode;
 import cn.hutool.crypto.Padding;
@@ -12,13 +13,25 @@ import cn.hutool.crypto.SecureUtil;
  * AES加密算法实现<br>
  * 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法<br>
  * 对于Java中AES的默认模式是:AES/ECB/PKCS5Padding,如果使用CryptoJS,请调整为:padding: CryptoJS.pad.Pkcs7
- * 
+ *
+ * <p>
+ * 相关概念说明:
+ * <pre>
+ * mode:    加密算法模式,是用来描述加密算法(此处特指分组密码,不包括流密码,)在加密时对明文分组的模式,它代表了不同的分组方式
+ * padding: 补码方式是在分组密码中,当明文长度不是分组长度的整数倍时,需要在最后一个分组中填充一些数据使其凑满一个分组的长度。
+ * iv:      在对明文分组加密时,会将明文分组与前一个密文分组进行XOR运算(即异或运算),但是加密第一个明文分组时不存在“前一个密文分组”,
+ *          因此需要事先准备一个与分组长度相等的比特序列来代替,这个比特序列就是偏移量。
+ * </pre>
+ * <p>
+ * 相关概念见:https://blog.csdn.net/OrangeJack/article/details/82913804
+ *
  * @author Looly
  * @since 3.0.8
  */
 public class AES extends SymmetricCrypto {
 
 	//------------------------------------------------------------------------- Constrctor start
+
 	/**
 	 * 构造,默认AES/ECB/PKCS5Padding,使用随机密钥
 	 */
@@ -28,7 +41,7 @@ public class AES extends SymmetricCrypto {
 
 	/**
 	 * 构造,使用默认的AES/ECB/PKCS5Padding
-	 * 
+	 *
 	 * @param key 密钥
 	 */
 	public AES(byte[] key) {
@@ -37,20 +50,20 @@ public class AES extends SymmetricCrypto {
 
 	/**
 	 * 构造,使用随机密钥
-	 * 
-	 * @param mode 模式{@link Mode}
+	 *
+	 * @param mode    模式{@link Mode}
 	 * @param padding {@link Padding}补码方式
 	 */
 	public AES(Mode mode, Padding padding) {
 		this(mode.name(), padding.name());
 	}
-	
+
 	/**
 	 * 构造
-	 * 
-	 * @param mode 模式{@link Mode}
+	 *
+	 * @param mode    模式{@link Mode}
 	 * @param padding {@link Padding}补码方式
-	 * @param key 密钥,支持三种密钥长度:128、192、256位
+	 * @param key     密钥,支持三种密钥长度:128、192、256位
 	 */
 	public AES(Mode mode, Padding padding, byte[] key) {
 		this(mode, padding, key, null);
@@ -58,36 +71,49 @@ public class AES extends SymmetricCrypto {
 
 	/**
 	 * 构造
-	 * 
-	 * @param mode 模式{@link Mode}
+	 *
+	 * @param mode    模式{@link Mode}
 	 * @param padding {@link Padding}补码方式
-	 * @param key 密钥,支持三种密钥长度:128、192、256位
-	 * @param iv 偏移向量,加盐
+	 * @param key     密钥,支持三种密钥长度:128、192、256位
+	 * @param iv      偏移向量,加盐
 	 * @since 3.3.0
 	 */
 	public AES(Mode mode, Padding padding, byte[] key, byte[] iv) {
 		this(mode.name(), padding.name(), key, iv);
 	}
-	
+
 	/**
 	 * 构造
-	 * 
-	 * @param mode 模式{@link Mode}
+	 *
+	 * @param mode    模式{@link Mode}
 	 * @param padding {@link Padding}补码方式
-	 * @param key 密钥,支持三种密钥长度:128、192、256位
+	 * @param key     密钥,支持三种密钥长度:128、192、256位
 	 * @since 3.3.0
 	 */
 	public AES(Mode mode, Padding padding, SecretKey key) {
-		this(mode, padding, key, null);
+		this(mode, padding, key, (IvParameterSpec) null);
 	}
 
 	/**
 	 * 构造
-	 * 
-	 * @param mode 模式{@link Mode}
+	 *
+	 * @param mode    模式{@link Mode}
 	 * @param padding {@link Padding}补码方式
-	 * @param key 密钥,支持三种密钥长度:128、192、256位
-	 * @param iv 偏移向量,加盐
+	 * @param key     密钥,支持三种密钥长度:128、192、256位
+	 * @param iv      偏移向量,加盐
+	 * @since 4.6.7
+	 */
+	public AES(Mode mode, Padding padding, SecretKey key, byte[] iv) {
+		this(mode, padding, key, ArrayUtil.isEmpty(iv) ? ((IvParameterSpec) null) : new IvParameterSpec(iv));
+	}
+
+	/**
+	 * 构造
+	 *
+	 * @param mode    模式{@link Mode}
+	 * @param padding {@link Padding}补码方式
+	 * @param key     密钥,支持三种密钥长度:128、192、256位
+	 * @param iv      偏移向量,加盐
 	 * @since 3.3.0
 	 */
 	public AES(Mode mode, Padding padding, SecretKey key, IvParameterSpec iv) {
@@ -96,8 +122,8 @@ public class AES extends SymmetricCrypto {
 
 	/**
 	 * 构造
-	 * 
-	 * @param mode 模式
+	 *
+	 * @param mode    模式
 	 * @param padding 补码方式
 	 */
 	public AES(String mode, String padding) {
@@ -106,10 +132,10 @@ public class AES extends SymmetricCrypto {
 
 	/**
 	 * 构造
-	 * 
-	 * @param mode 模式
+	 *
+	 * @param mode    模式
 	 * @param padding 补码方式
-	 * @param key 密钥,支持三种密钥长度:128、192、256位
+	 * @param key     密钥,支持三种密钥长度:128、192、256位
 	 */
 	public AES(String mode, String padding, byte[] key) {
 		this(mode, padding, key, null);
@@ -117,22 +143,24 @@ public class AES extends SymmetricCrypto {
 
 	/**
 	 * 构造
-	 * 
-	 * @param mode 模式
+	 *
+	 * @param mode    模式
 	 * @param padding 补码方式
-	 * @param key 密钥,支持三种密钥长度:128、192、256位
-	 * @param iv 加盐
+	 * @param key     密钥,支持三种密钥长度:128、192、256位
+	 * @param iv      加盐
 	 */
 	public AES(String mode, String padding, byte[] key, byte[] iv) {
-		this(mode, padding, SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), key), null == iv ? null : new IvParameterSpec(iv));
+		this(mode, padding,//
+				SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), key),//
+				ArrayUtil.isEmpty(iv) ? ((IvParameterSpec) null) : new IvParameterSpec(iv));
 	}
-	
+
 	/**
 	 * 构造
-	 * 
-	 * @param mode 模式
+	 *
+	 * @param mode    模式
 	 * @param padding 补码方式
-	 * @param key 密钥,支持三种密钥长度:128、192、256位
+	 * @param key     密钥,支持三种密钥长度:128、192、256位
 	 */
 	public AES(String mode, String padding, SecretKey key) {
 		this(mode, padding, key, null);
@@ -140,11 +168,11 @@ public class AES extends SymmetricCrypto {
 
 	/**
 	 * 构造
-	 * 
-	 * @param mode 模式
+	 *
+	 * @param mode    模式
 	 * @param padding 补码方式
-	 * @param key 密钥,支持三种密钥长度:128、192、256位
-	 * @param iv 加盐
+	 * @param key     密钥,支持三种密钥长度:128、192、256位
+	 * @param iv      加盐
 	 */
 	public AES(String mode, String padding, SecretKey key, IvParameterSpec iv) {
 		super(StrUtil.format("AES/{}/{}", mode, padding), key, iv);
@@ -153,7 +181,7 @@ public class AES extends SymmetricCrypto {
 
 	/**
 	 * 设置偏移向量
-	 * 
+	 *
 	 * @param iv {@link IvParameterSpec}偏移向量
 	 * @return 自身
 	 */
@@ -161,10 +189,10 @@ public class AES extends SymmetricCrypto {
 		super.setParams(iv);
 		return this;
 	}
-	
+
 	/**
 	 * 设置偏移向量
-	 * 
+	 *
 	 * @param iv 偏移向量,加盐
 	 * @return 自身
 	 * @since 3.3.0

+ 161 - 77
hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java

@@ -1,49 +1,57 @@
 package cn.hutool.crypto.symmetric;
 
-import java.io.InputStream;
-import java.nio.charset.Charset;
-import java.security.spec.AlgorithmParameterSpec;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.PBEParameterSpec;
-
 import cn.hutool.core.codec.Base64;
 import cn.hutool.core.io.IORuntimeException;
 import cn.hutool.core.io.IoUtil;
-import cn.hutool.core.util.CharsetUtil;
-import cn.hutool.core.util.HexUtil;
-import cn.hutool.core.util.RandomUtil;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.*;
 import cn.hutool.crypto.CryptoException;
 import cn.hutool.crypto.KeyUtil;
+import cn.hutool.crypto.Padding;
 import cn.hutool.crypto.SecureUtil;
 
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.PBEParameterSpec;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
 /**
  * 对称加密算法<br>
  * 在对称加密算法中,数据发信方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。<br>
  * 收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。<br>
  * 在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。<br>
- * 
- * @author Looly
  *
+ * @author Looly
  */
 public class SymmetricCrypto {
 
-	/** SecretKey 负责保存对称密钥 */
+	/**
+	 * SecretKey 负责保存对称密钥
+	 */
 	private SecretKey secretKey;
-	/** Cipher负责完成加密或解密工作 */
+	/**
+	 * Cipher负责完成加密或解密工作
+	 */
 	private Cipher cipher;
-	/** 加密解密参数 */
+	/**
+	 * 加密解密参数
+	 */
 	private AlgorithmParameterSpec params;
+	/**
+	 * 是否0填充
+	 */
+	private boolean isZeroPadding;
 	private Lock lock = new ReentrantLock();
 
 	// ------------------------------------------------------------------ Constructor start
+
 	/**
 	 * 构造,使用随机密钥
-	 * 
+	 *
 	 * @param algorithm {@link SymmetricAlgorithm}
 	 */
 	public SymmetricCrypto(SymmetricAlgorithm algorithm) {
@@ -52,7 +60,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 构造,使用随机密钥
-	 * 
+	 *
 	 * @param algorithm 算法,可以是"algorithm/mode/padding"或者"algorithm"
 	 */
 	public SymmetricCrypto(String algorithm) {
@@ -61,9 +69,9 @@ public class SymmetricCrypto {
 
 	/**
 	 * 构造
-	 * 
+	 *
 	 * @param algorithm 算法 {@link SymmetricAlgorithm}
-	 * @param key 自定义KEY
+	 * @param key       自定义KEY
 	 */
 	public SymmetricCrypto(SymmetricAlgorithm algorithm, byte[] key) {
 		this(algorithm.getValue(), key);
@@ -71,9 +79,9 @@ public class SymmetricCrypto {
 
 	/**
 	 * 构造
-	 * 
+	 *
 	 * @param algorithm 算法 {@link SymmetricAlgorithm}
-	 * @param key 自定义KEY
+	 * @param key       自定义KEY
 	 * @since 3.1.2
 	 */
 	public SymmetricCrypto(SymmetricAlgorithm algorithm, SecretKey key) {
@@ -82,9 +90,9 @@ public class SymmetricCrypto {
 
 	/**
 	 * 构造
-	 * 
+	 *
 	 * @param algorithm 算法
-	 * @param key 密钥
+	 * @param key       密钥
 	 */
 	public SymmetricCrypto(String algorithm, byte[] key) {
 		this(algorithm, KeyUtil.generateKey(algorithm, key));
@@ -92,9 +100,9 @@ public class SymmetricCrypto {
 
 	/**
 	 * 构造
-	 * 
+	 *
 	 * @param algorithm 算法
-	 * @param key 密钥
+	 * @param key       密钥
 	 * @since 3.1.2
 	 */
 	public SymmetricCrypto(String algorithm, SecretKey key) {
@@ -103,9 +111,9 @@ public class SymmetricCrypto {
 
 	/**
 	 * 构造
-	 * 
-	 * @param algorithm 算法
-	 * @param key 密钥
+	 *
+	 * @param algorithm  算法
+	 * @param key        密钥
 	 * @param paramsSpec 算法参数,例如加盐等
 	 * @since 3.3.0
 	 */
@@ -117,26 +125,36 @@ public class SymmetricCrypto {
 	}
 
 	// ------------------------------------------------------------------ Constructor end
+
 	/**
 	 * 初始化
-	 * 
+	 *
 	 * @param algorithm 算法
-	 * @param key 密钥,如果为<code>null</code>自动生成一个key
+	 * @param key       密钥,如果为<code>null</code>自动生成一个key
 	 * @return {@link SymmetricCrypto}
 	 */
 	public SymmetricCrypto init(String algorithm, SecretKey key) {
+		Assert.notBlank(algorithm, "'algorithm' must be not blank !");
 		this.secretKey = key;
+
+		// 对于PBE算法使用随机数加盐
 		if (algorithm.startsWith("PBE")) {
-			// 对于PBE算法使用随机数加盐
 			this.params = new PBEParameterSpec(RandomUtil.randomBytes(8), 100);
 		}
+
+		// 检查是否为ZeroPadding,是则替换为NoPadding,并标记以便单独处理
+		if (algorithm.contains(Padding.ZeroPadding.name())) {
+			algorithm = StrUtil.replace(algorithm, Padding.ZeroPadding.name(), Padding.NoPadding.name());
+			this.isZeroPadding = true;
+		}
+
 		this.cipher = SecureUtil.createCipher(algorithm);
 		return this;
 	}
 
 	/**
 	 * 设置 {@link AlgorithmParameterSpec},通常用于加盐或偏移向量
-	 * 
+	 *
 	 * @param params {@link AlgorithmParameterSpec}
 	 * @return 自身
 	 */
@@ -146,9 +164,10 @@ public class SymmetricCrypto {
 	}
 
 	// --------------------------------------------------------------------------------- Encrypt
+
 	/**
 	 * 加密
-	 * 
+	 *
 	 * @param data 被加密的bytes
 	 * @return 加密后的bytes
 	 */
@@ -160,7 +179,7 @@ public class SymmetricCrypto {
 			} else {
 				cipher.init(Cipher.ENCRYPT_MODE, secretKey, params);
 			}
-			return cipher.doFinal(data);
+			return cipher.doFinal(paddingDataWithZero(data, cipher.getBlockSize()));
 		} catch (Exception e) {
 			throw new CryptoException(e);
 		} finally {
@@ -170,7 +189,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密
-	 * 
+	 *
 	 * @param data 数据
 	 * @return 加密后的Hex
 	 */
@@ -180,7 +199,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密
-	 * 
+	 *
 	 * @param data 数据
 	 * @return 加密后的Base64
 	 * @since 4.0.1
@@ -191,19 +210,19 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密
-	 * 
-	 * @param data 被加密的字符串
+	 *
+	 * @param data    被加密的字符串
 	 * @param charset 编码
 	 * @return 加密后的bytes
 	 */
 	public byte[] encrypt(String data, String charset) {
 		return encrypt(StrUtil.bytes(data, charset));
 	}
-	
+
 	/**
 	 * 加密
-	 * 
-	 * @param data 被加密的字符串
+	 *
+	 * @param data    被加密的字符串
 	 * @param charset 编码
 	 * @return 加密后的bytes
 	 */
@@ -213,8 +232,8 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密
-	 * 
-	 * @param data 被加密的字符串
+	 *
+	 * @param data    被加密的字符串
 	 * @param charset 编码
 	 * @return 加密后的Hex
 	 * @since 4.5.12
@@ -222,11 +241,11 @@ public class SymmetricCrypto {
 	public String encryptHex(String data, String charset) {
 		return HexUtil.encodeHexStr(encrypt(data, charset));
 	}
-	
+
 	/**
 	 * 加密
-	 * 
-	 * @param data 被加密的字符串
+	 *
+	 * @param data    被加密的字符串
 	 * @param charset 编码
 	 * @return 加密后的Hex
 	 * @since 4.5.12
@@ -237,19 +256,19 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密
-	 * 
-	 * @param data 被加密的字符串
+	 *
+	 * @param data    被加密的字符串
 	 * @param charset 编码
 	 * @return 加密后的Base64
 	 */
 	public String encryptBase64(String data, String charset) {
 		return Base64.encode(encrypt(data, charset));
 	}
-	
+
 	/**
 	 * 加密
-	 * 
-	 * @param data 被加密的字符串
+	 *
+	 * @param data    被加密的字符串
 	 * @param charset 编码
 	 * @return 加密后的Base64
 	 * @since 4.5.12
@@ -260,7 +279,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密,使用UTF-8编码
-	 * 
+	 *
 	 * @param data 被加密的字符串
 	 * @return 加密后的bytes
 	 */
@@ -270,7 +289,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密,使用UTF-8编码
-	 * 
+	 *
 	 * @param data 被加密的字符串
 	 * @return 加密后的Hex
 	 */
@@ -280,7 +299,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密,使用UTF-8编码
-	 * 
+	 *
 	 * @param data 被加密的字符串
 	 * @return 加密后的Base64
 	 */
@@ -290,7 +309,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密
-	 * 
+	 *
 	 * @param data 被加密的字符串
 	 * @return 加密后的bytes
 	 * @throws IORuntimeException IO异常
@@ -301,7 +320,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密
-	 * 
+	 *
 	 * @param data 被加密的字符串
 	 * @return 加密后的Hex
 	 */
@@ -311,7 +330,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 加密
-	 * 
+	 *
 	 * @param data 被加密的字符串
 	 * @return 加密后的Base64
 	 */
@@ -320,13 +339,17 @@ public class SymmetricCrypto {
 	}
 
 	// --------------------------------------------------------------------------------- Decrypt
+
 	/**
 	 * 解密
-	 * 
+	 *
 	 * @param bytes 被解密的bytes
 	 * @return 解密后的bytes
 	 */
 	public byte[] decrypt(byte[] bytes) {
+		final int blockSize;
+		final byte[] decryptData;
+
 		lock.lock();
 		try {
 			if (null == this.params) {
@@ -334,18 +357,21 @@ public class SymmetricCrypto {
 			} else {
 				cipher.init(Cipher.DECRYPT_MODE, secretKey, params);
 			}
-			return cipher.doFinal(bytes);
+			blockSize = cipher.getBlockSize();
+			decryptData = cipher.doFinal(bytes);
 		} catch (Exception e) {
 			throw new CryptoException(e);
 		} finally {
 			lock.unlock();
 		}
+
+		return removePadding(decryptData, blockSize);
 	}
 
 	/**
 	 * 解密为字符串
-	 * 
-	 * @param bytes 被解密的bytes
+	 *
+	 * @param bytes   被解密的bytes
 	 * @param charset 解密后的charset
 	 * @return 解密后的String
 	 */
@@ -355,7 +381,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 解密为字符串,默认UTF-8编码
-	 * 
+	 *
 	 * @param bytes 被解密的bytes
 	 * @return 解密后的String
 	 */
@@ -365,7 +391,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 解密Hex(16进制)或Base64表示的字符串
-	 * 
+	 *
 	 * @param data 被解密的String,必须为16进制字符串或Base64表示形式
 	 * @return 解密后的bytes
 	 */
@@ -375,8 +401,8 @@ public class SymmetricCrypto {
 
 	/**
 	 * 解密Hex(16进制)或Base64表示的字符串
-	 * 
-	 * @param data 被解密的String
+	 *
+	 * @param data    被解密的String
 	 * @param charset 解密后的charset
 	 * @return 解密后的String
 	 */
@@ -386,7 +412,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 解密Hex表示的字符串,默认UTF-8编码
-	 * 
+	 *
 	 * @param data 被解密的String
 	 * @return 解密后的String
 	 */
@@ -396,7 +422,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 解密,不会关闭流
-	 * 
+	 *
 	 * @param data 被解密的bytes
 	 * @return 解密后的bytes
 	 * @throws IORuntimeException IO异常
@@ -407,8 +433,8 @@ public class SymmetricCrypto {
 
 	/**
 	 * 解密,不会关闭流
-	 * 
-	 * @param data 被解密的InputStream
+	 *
+	 * @param data    被解密的InputStream
 	 * @param charset 解密后的charset
 	 * @return 解密后的String
 	 */
@@ -418,7 +444,7 @@ public class SymmetricCrypto {
 
 	/**
 	 * 解密
-	 * 
+	 *
 	 * @param data 被解密的InputStream
 	 * @return 解密后的String
 	 */
@@ -427,9 +453,10 @@ public class SymmetricCrypto {
 	}
 
 	// --------------------------------------------------------------------------------- Getters
+
 	/**
 	 * 获得对称密钥
-	 * 
+	 *
 	 * @return 获得对称密钥
 	 */
 	public SecretKey getSecretKey() {
@@ -438,10 +465,67 @@ public class SymmetricCrypto {
 
 	/**
 	 * 获得加密或解密器
-	 * 
+	 *
 	 * @return 加密或解密
 	 */
-	public Cipher getClipher() {
+	public Cipher getCipher() {
 		return cipher;
 	}
+
+	// --------------------------------------------------------------------------------- Private method start
+
+	/**
+	 * 数据按照blockSize的整数倍长度填充填充0
+	 *
+	 * <p>
+	 * 在{@link Padding#ZeroPadding} 模式下,且数据长度不是blockSize的整数倍才有效,否则返回原数据
+	 *
+	 * <p>
+	 * 见:https://blog.csdn.net/OrangeJack/article/details/82913804
+	 *
+	 * @param data      数据
+	 * @param blockSize 块大小
+	 * @return 填充后的数据,如果isZeroPadding为false或长度刚好,返回原数据
+	 * @since 4.6.7
+	 */
+	private byte[] paddingDataWithZero(byte[] data, int blockSize) {
+		if (this.isZeroPadding) {
+			final int length = data.length;
+			// 按照块拆分后的数据中多余的数据
+			final int remainLength = length % blockSize;
+			if (remainLength > 0) {
+				// 新长度为blockSize的整数倍,多余部分填充0
+				return ArrayUtil.resize(data, length + blockSize - remainLength);
+			}
+		}
+		return data;
+	}
+
+	/**
+	 * 数据按照blockSize去除填充部分,用于解密
+	 *
+	 * <p>
+	 * 在{@link Padding#ZeroPadding} 模式下,且数据长度不是blockSize的整数倍才有效,否则返回原数据
+	 *
+	 * @param data      数据
+	 * @param blockSize 块大小
+	 * @return 去除填充后的数据,如果isZeroPadding为false或长度刚好,返回原数据
+	 * @since 4.6.7
+	 */
+	private byte[] removePadding(byte[] data, int blockSize) {
+		if (this.isZeroPadding) {
+			final int length = data.length;
+			final int remainLength = length % blockSize;
+			if (remainLength == 0) {
+				// 解码后的数据正好是块大小的整数倍,说明可能存在补0的情况,去掉末尾所有的0
+				int i = length - 1;
+				while (i >= 0 && 0 == data[i]) {
+					i--;
+				}
+				return ArrayUtil.resize(data, i + 1);
+			}
+		}
+		return data;
+	}
+	// --------------------------------------------------------------------------------- Private method end
 }

+ 15 - 0
hutool-crypto/src/test/java/cn/hutool/crypto/test/SymmetricTest.java

@@ -1,5 +1,8 @@
 package cn.hutool.crypto.test;
 
+import cn.hutool.core.lang.Console;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.RandomUtil;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -107,6 +110,18 @@ public class SymmetricTest {
 	}
 
 	@Test
+	public void aesZeroPaddingTest() {
+		String content = RandomUtil.randomString(RandomUtil.randomInt(200));
+		AES aes = new AES(Mode.CBC, Padding.ZeroPadding, "0123456789ABHAEQ".getBytes(), "DYgjCEIMVrj2W9xN".getBytes());
+
+		// 加密为16进制表示
+		String encryptHex = aes.encryptHex(content);
+		// 解密
+		String decryptStr = aes.decryptStr(encryptHex);
+		Assert.assertEquals(content, decryptStr);
+	}
+
+	@Test
 	public void desTest() {
 		String content = "test中文";
 

+ 4 - 9
hutool-db/src/main/java/cn/hutool/db/Entity.java

@@ -6,10 +6,7 @@ import java.sql.Clob;
 import java.sql.RowId;
 import java.sql.Time;
 import java.sql.Timestamp;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.*;
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.lang.Dict;
@@ -156,7 +153,7 @@ public class Entity extends Dict {
 	 */
 	public Entity setFieldNames(Collection<String> fieldNames) {
 		if (CollectionUtil.isNotEmpty(fieldNames)) {
-			this.fieldNames = new HashSet<String>(fieldNames);
+			this.fieldNames = CollectionUtil.newHashSet(true, fieldNames);
 		}
 		return this;
 	}
@@ -169,7 +166,7 @@ public class Entity extends Dict {
 	 */
 	public Entity setFieldNames(String... fieldNames) {
 		if (ArrayUtil.isNotEmpty(fieldNames)) {
-			this.fieldNames = CollectionUtil.newHashSet(fieldNames);
+			this.fieldNames = CollectionUtil.newLinkedHashSet(fieldNames);
 		}
 		return this;
 	}
@@ -185,9 +182,7 @@ public class Entity extends Dict {
 			if (null == this.fieldNames) {
 				return setFieldNames(fieldNames);
 			} else {
-				for (String fieldName : fieldNames) {
-					this.fieldNames.add(fieldName);
-				}
+				Collections.addAll(this.fieldNames, fieldNames);
 			}
 		}
 		return this;