diff --git a/CHANGELOG.md b/CHANGELOG.md index 827b6eca5..2204a1e39 100644 --- a/CHANGELOG.md +++ b/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) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index c05c8bed7..33ab3dcfd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/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 数组元素类型 - * @param buffer 原数组 + * @param data 原数组 * @param newSize 新的数组大小 * @param componentType 数组元素类型 * @return 调整后的新数组 */ - public static 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[] 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; + } + + /** + * 生成一个新的重新设置大小的数组
+ * 调整大小后拷贝原数组到新数组下。扩大则占位前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; + } + + /** + * 生成一个新的重新设置大小的数组
+ * 调整大小后拷贝原数组到新数组下。扩大则占位前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; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java index f49888325..3e65c83f0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java @@ -337,7 +337,7 @@ public class RandomUtil { * @return 随机元素 */ public static List randomEles(List list, int count) { - final List result = new ArrayList(count); + final List 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 result = new HashSet(count); + final HashSet 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())); } /** diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/Mode.java b/hutool-crypto/src/main/java/cn/hutool/crypto/Mode.java index a9a1fc0b7..4a71e3473 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/Mode.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/Mode.java @@ -2,25 +2,45 @@ package cn.hutool.crypto; /** * 模式 + * + *

+ * 加密算法模式,是用来描述加密算法(此处特指分组密码,不包括流密码,)在加密时对明文分组的模式,它代表了不同的分组方式 + * * @author Looly * @see Cipher章节 * @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; } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/Padding.java b/hutool-crypto/src/main/java/cn/hutool/crypto/Padding.java index cbf5a5757..7a06c4d07 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/Padding.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/Padding.java @@ -2,17 +2,26 @@ package cn.hutool.crypto; /** * 补码方式 - * + * + *

+ * 补码方式是在分组密码中,当明文长度不是分组长度的整数倍时,需要在最后一个分组中填充一些数据使其凑满一个分组的长度。 + * * @author Looly * @see Cipher章节 * @since 3.0.8 */ public enum Padding { - /** 无补码 */ - NoPadding, + /** + * 无补码 + */ + NoPadding, + /** + * 0补码,既不满block长度时使用0填充 + */ + ZeroPadding, ISO10126Padding, OAEPPadding, PKCS1Padding, - PKCS5Padding, + PKCS5Padding, SSL3Padding } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java index 4472fc06f..b3c6170bc 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java +++ b/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加密算法实现
* 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法
* 对于Java中AES的默认模式是:AES/ECB/PKCS5Padding,如果使用CryptoJS,请调整为:padding: CryptoJS.pad.Pkcs7 - * + * + *

+ * 相关概念说明: + *

+ * mode:    加密算法模式,是用来描述加密算法(此处特指分组密码,不包括流密码,)在加密时对明文分组的模式,它代表了不同的分组方式
+ * padding: 补码方式是在分组密码中,当明文长度不是分组长度的整数倍时,需要在最后一个分组中填充一些数据使其凑满一个分组的长度。
+ * iv:      在对明文分组加密时,会将明文分组与前一个密文分组进行XOR运算(即异或运算),但是加密第一个明文分组时不存在“前一个密文分组”,
+ *          因此需要事先准备一个与分组长度相等的比特序列来代替,这个比特序列就是偏移量。
+ * 
+ *

+ * 相关概念见: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 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java index d493421c3..79a684c69 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java @@ -1,49 +1,57 @@ package cn.hutool.crypto.symmetric; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +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; -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.crypto.CryptoException; -import cn.hutool.crypto.KeyUtil; -import cn.hutool.crypto.SecureUtil; - /** * 对称加密算法
* 在对称加密算法中,数据发信方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。
* 收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。
* 在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。
- * - * @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 密钥,如果为null自动生成一个key + * @param key 密钥,如果为null自动生成一个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 + * + *

+ * 在{@link Padding#ZeroPadding} 模式下,且数据长度不是blockSize的整数倍才有效,否则返回原数据 + * + *

+ * 见: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去除填充部分,用于解密 + * + *

+ * 在{@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 } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SymmetricTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SymmetricTest.java index d4fedc5f1..fac385ae4 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SymmetricTest.java +++ b/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; @@ -106,6 +109,18 @@ public class SymmetricTest { Assert.assertEquals("cd0e3a249eaf0ed80c330338508898c4bddcfd665a1b414622164a273ca5daf7b4ebd2c00aaa66b84dd0a237708dac8e", encryptHex); } + @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中文"; diff --git a/hutool-db/src/main/java/cn/hutool/db/Entity.java b/hutool-db/src/main/java/cn/hutool/db/Entity.java index 85cdf2db1..1b5e22707 100644 --- a/hutool-db/src/main/java/cn/hutool/db/Entity.java +++ b/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 fieldNames) { if (CollectionUtil.isNotEmpty(fieldNames)) { - this.fieldNames = new HashSet(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;