From 7f8c46fa9b2b2baead953f84ed4898c48addf988 Mon Sep 17 00:00:00 2001 From: maxf Date: Fri, 12 Dec 2025 18:02:13 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20=E8=BF=9B=E8=A1=8C=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=B7=A5=E4=BD=9C/=F0=9F=94=84=20=E9=87=8D=E6=9E=84/?= =?UTF-8?q?=E2=9C=85=20=E6=B5=8B=E8=AF=95:=20=E4=BC=98=E5=8C=96AES?= =?UTF-8?q?=E3=80=81DES3=E5=92=8CRSA=E5=8A=A0=E5=AF=86=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=B9=B6=E5=A2=9E=E5=BC=BA=E5=AE=89=E5=85=A8=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改DateConsts中的ISO_8601时间格式,使用XXX代替xxx以符合标准时区偏移表示 - 完善AES类注释及文档说明,明确加密模式支持情况和参数要求 - 优化AES加解密逻辑,去除不必要的数据块填充操作,直接使用原始数据进行加解密 - 增强AES异常处理机制,统一捕获并封装异常防止信息泄露 - 在AES中增加获取当前算法的方法,并完善各个getter/setter方法的注释和校验 - 重构DES3类,改进密钥长度验证逻辑,确保密钥非空且满足最低长度要求 - 改进DES3加密过程,使用SecureRandom生成随机IV并向量与加密结果合并存储 - 更新DES3解密流程,从加密数据中分离出IV用于解密,提高安全性 - 添加generateKey私有方法用于生成3DES密钥对象,集中管理密钥创建过程 - 优化DES3填充方法padding,当数据长度恰好为8的倍数时不进行多余填充 - 修订RSA类结构,将内部静态类Instace更名为Instance并调整访问权限 - 修改RSA字段为volatile类型,确保多线程环境下的可见性和一致性 - 完善RSA密钥生成方法initKeys,支持临时设置Base64URLSafe标志并在执行后恢复原值 - 优化RSA密钥文件写入方式,使用Files.writeString替代旧版方法提升性能 - 补充RSA获取公私钥方法的返回值说明和可能抛出的具体异常类型 - 改进RSA加密解密方法,支持传入base64URLSafe参数控制编码格式 - 重构RSA核心加解密逻辑encrypt/decrypt,抽取getKeyLength方法计算密钥长度 - 引入BouncyCastleProvider支持特定SHA3签名算法的RSA实现 - 增强RSA分段编解码rsaSplitCodec方法,加入最大块大小有效性检查 - 优化RSA签名verify方法,提取复合签名字符串解析逻辑至独立extractSignature方法 - 完善RSA2各类获取公私钥方法的返回值说明和详细的异常描述 - 修复RSA2中JcaPEMKeyConverter未指定Provider的问题,统一使用BouncyCastleProvider - 整体增强各加密类的安全性、健壮性和代码可维护性 --- README.md | 6 +- UPDATE.md | 8 + pom.xml | 4 +- .../com/yexuejc/base/constant/DateConsts.java | 4 +- .../java/com/yexuejc/base/encrypt/AES.java | 68 +++++- .../java/com/yexuejc/base/encrypt/DES3.java | 118 ++++++---- .../java/com/yexuejc/base/encrypt/RSA.java | 211 +++++++++++------ .../java/com/yexuejc/base/encrypt/RSA2.java | 91 +++---- .../com/yexuejc/base/encrypt/RSACoder.java | 194 ++++++++------- .../yexuejc/base/exception/BaseException.java | 6 +- .../java/com/yexuejc/base/file/FileInput.java | 140 ++++++----- .../com/yexuejc/base/util/DateTimeUtil.java | 111 +++------ .../com/yexuejc/base/encrypt/AESTest.java | 222 +++++++++++++++--- .../com/yexuejc/base/encrypt/DES3NewTest.java | 187 +++++++++++++++ .../com/yexuejc/base/encrypt/DES3Test.java | 141 +++++++++++ .../base/encrypt/PaddingDebugTest.java | 23 ++ .../com/yexuejc/base/encrypt/RSA2Test.java | 179 ++++++++++++++ .../com/yexuejc/base/encrypt/RSATestNew.java | 180 ++++++++++++++ 18 files changed, 1442 insertions(+), 451 deletions(-) create mode 100644 src/test/java/com/yexuejc/base/encrypt/DES3NewTest.java create mode 100644 src/test/java/com/yexuejc/base/encrypt/DES3Test.java create mode 100644 src/test/java/com/yexuejc/base/encrypt/PaddingDebugTest.java create mode 100644 src/test/java/com/yexuejc/base/encrypt/RSA2Test.java create mode 100644 src/test/java/com/yexuejc/base/encrypt/RSATestNew.java diff --git a/README.md b/README.md index 219c620..a13b92f 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ github:https://github.com/yexuejc/yexuejc-base gitee:https://gitee.com/jzsw-it/yexuejc-base ### 说明 -1. 支持环境:java11(1.5.0开始支持java11,请使用`1.5.x-jre11`版本) +1. 支持环境:java21(1.5.0开始支持java11,请使用`1.5.x-jre11`版本,1.6.0开始支持java21,请使用`1.6.x-jre21`版本) 2. 该工具包基于springboot提取,按理说适用于所有java工程 -7. 从`1.5.0`开始,版本分为`1.5.0-jre8`和`1.5.0-jre11`,分别对于jre8和jre11使用(后续逐渐放弃jre8) +7. 从`1.5.0`开始,版本分为`1.5.0-jre8`和`1.5.0-jre11`和`1.6.0-jre21`,分别对于jre8和jre11和jre21使用(后续逐渐全面使用jre21) ### 使用 @@ -17,7 +17,7 @@ pom.xml top.yexuejc yexuejc-base - 1.5.2-jre11 + 1.6.0-jre21 ``` diff --git a/UPDATE.md b/UPDATE.md index 48bda40..d4fbf2d 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -1,5 +1,13 @@ yexuejc-base 更新记录 ------------------ +#### version :1.6.0-jre21 +**time: 2025-12-11 18:23:15**
+**branch:** jre21
+**update:**
+1. 全面升级到JDK21 + +--- + #### version :1.5.7-jre11 **time: 2025-12-11 18:10:47**
**branch:** jre11
diff --git a/pom.xml b/pom.xml index 2216e53..f7984c9 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ top.yexuejc yexuejc-base - 1.5.7-jre11 + 1.6.0-jre21 yexuejc-base https://github.com/yexuejc/yexuejc-base @@ -39,7 +39,7 @@ - 11 + 21 ${java.version} ${java.version} true diff --git a/src/main/java/com/yexuejc/base/constant/DateConsts.java b/src/main/java/com/yexuejc/base/constant/DateConsts.java index 16ba549..5d0ab31 100644 --- a/src/main/java/com/yexuejc/base/constant/DateConsts.java +++ b/src/main/java/com/yexuejc/base/constant/DateConsts.java @@ -18,8 +18,8 @@ public class DateConsts { public static final String TIME_PATTERN = "HH:mm:ss"; public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; public static final String DATE_TIME_MS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; - public static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ssxxx"; - public static final String ISO_8601_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSxxx"; + public static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ssXXX"; + public static final String ISO_8601_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; public static final String DATE_YYYYMMDD_PATTERN = "yyyyMMdd"; } diff --git a/src/main/java/com/yexuejc/base/encrypt/AES.java b/src/main/java/com/yexuejc/base/encrypt/AES.java index 3858263..4479694 100644 --- a/src/main/java/com/yexuejc/base/encrypt/AES.java +++ b/src/main/java/com/yexuejc/base/encrypt/AES.java @@ -16,12 +16,13 @@ import com.yexuejc.base.util.StrUtil; * * @author maxf * @class-name AES - * @description + * @description 提供AES对称加密解密功能,支持多种加密模式和填充方式 * @date 2022/11/11 15:36 */ public class AES { /** * 创建新的AES实例 + * * @return 新的AES实例 */ public static AES builder() { @@ -105,7 +106,7 @@ public class AES { * 加密 * * @param data 明文 - * @return 密文 + * @return 密文(Base64编码格式) * @throws BaseException 加密异常 * @Description AES算法加密明文 */ @@ -113,23 +114,15 @@ public class AES { validateKeyAndIv(); validateInput(data, "加密数据"); try { - Cipher cipher = Cipher.getInstance(algorithm.code); - int blockSize = cipher.getBlockSize(); - byte[] dataBytes = data.getBytes(charset); - int plaintextLength = dataBytes.length; - if (plaintextLength % blockSize != 0) { - plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize)); - } - byte[] plaintext = new byte[plaintextLength]; - System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length); SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(charset), AES_ALGORITHM); IvParameterSpec ivspec = null; + // ECB模式不需要初始化向量 if (!algorithm.code.contains("ECB")) { ivspec = new IvParameterSpec(iv.getBytes(charset)); } cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec); - byte[] encrypted = cipher.doFinal(plaintext); + byte[] encrypted = cipher.doFinal(data.getBytes(charset)); return Base64.getEncoder().encodeToString(encrypted); } catch (Exception e) { throw new BaseException(e, ExpCode.ENCRYPTION_FAILED); @@ -139,7 +132,7 @@ public class AES { /** * 解密 * - * @param data 密文 + * @param data 密文(Base64编码格式) * @return 明文 * @throws BaseException 解密异常 * @Description AES算法解密密文 @@ -152,6 +145,7 @@ public class AES { Cipher cipher = Cipher.getInstance(algorithm.code); SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(charset), AES_ALGORITHM); IvParameterSpec ivspec = null; + // ECB模式不需要初始化向量 if (!algorithm.code.contains("ECB")) { ivspec = new IvParameterSpec(iv.getBytes(charset)); } @@ -159,10 +153,16 @@ public class AES { byte[] original = cipher.doFinal(encrypted); return new String(original, charset).trim(); } catch (Exception e) { + // 统一异常处理,防止信息泄露 throw new BaseException(e, ExpCode.DECRYPTION_PARAM_FAILED, data); } } + /** + * 获取当前使用的加密算法 + * + * @return 当前算法枚举值 + */ public ALGORITHM getAlgorithm() { return algorithm; } @@ -172,30 +172,64 @@ public class AES { return this; } + /** + * 获取加密密钥 + * + * @return 加密密钥 + */ public String getKey() { return key; } + /** + * 设置加密密钥 + * + * @param key 加密密钥,长度必须是16、24或32字节 + * @return 当前AES实例,支持链式调用 + */ public AES setKey(String key) { validateKeyLength(key); this.key = key; return this; } + /** + * 获取初始化向量(IV) + * + * @return 初始化向量 + */ public String getIv() { return iv; } + /** + * 设置初始化向量(IV) + * + * @param iv 初始化向量,长度必须是16字节 + * @return 当前AES实例,支持链式调用 + */ public AES setIv(String iv) { validateIvLength(iv); this.iv = iv; return this; } + /** + * 获取字符集编码 + * + * @return 字符集编码 + */ public Charset getCharset() { return charset; } + /** + * 设置字符集编码 + * + * @param charset 字符集编码 + * @return 当前AES实例,支持链式调用 + * @throws IllegalArgumentException 当字符集为空时抛出 + */ public AES setCharset(Charset charset) { if (charset == null) { throw new IllegalArgumentException("字符集不能为空"); @@ -218,6 +252,9 @@ public class AES { /** * 验证密钥长度 + * + * @param key 待验证的密钥 + * @throws IllegalArgumentException 当密钥为空或长度不符合要求时抛出 */ private void validateKeyLength(String key) { if (StrUtil.isEmpty(key)) { @@ -231,6 +268,9 @@ public class AES { /** * 验证IV长度 + * + * @param iv 待验证的初始化向量 + * @throws IllegalArgumentException 当IV为空或长度不符合要求时抛出 */ private void validateIvLength(String iv) { if (StrUtil.isEmpty(iv)) { @@ -253,6 +293,8 @@ public class AES { /** * 构造函数 - 允许创建多个实例 + *

+ * 允许创建多个实例,避免单例模式的状态共享问题 */ public AES() { // 允许创建多个实例,避免单例模式的状态共享问题 diff --git a/src/main/java/com/yexuejc/base/encrypt/DES3.java b/src/main/java/com/yexuejc/base/encrypt/DES3.java index 5bfa0e1..74d7750 100644 --- a/src/main/java/com/yexuejc/base/encrypt/DES3.java +++ b/src/main/java/com/yexuejc/base/encrypt/DES3.java @@ -1,15 +1,20 @@ package com.yexuejc.base.encrypt; -import com.yexuejc.base.constant.ExpCode; -import com.yexuejc.base.exception.BaseException; -import org.apache.commons.codec.binary.Base64; - +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; import javax.crypto.Cipher; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESedeKeySpec; import javax.crypto.spec.IvParameterSpec; -import java.nio.charset.StandardCharsets; -import java.security.Key; + +import com.yexuejc.base.constant.ExpCode; +import com.yexuejc.base.exception.BaseException; +import org.apache.commons.codec.binary.Base64; /** @@ -17,39 +22,45 @@ import java.security.Key; * * @author maxf * @ClassName ThreeDES - * @Description + * @Description 提供3DES对称加密解密功能,使用CBC模式和PKCS5填充 * @date 2018/9/3 17:09 */ public class DES3 { private DES3() { } - public static String IV = "1234567-"; - public static String ENCODING = "utf-8"; + private static final Charset CHARSET = StandardCharsets.UTF_8; + private static final String ALGORITHM = "desede"; + private static final String TRANSFORMATION = "desede/CBC/PKCS5Padding"; /** * DESCBC加密 * * @param src 数据源 - * @param key 密钥 + * @param key 密钥,长度至少24位 * @return 返回加密后的数据 - * @throws BaseException + * @throws BaseException 加密异常 */ public static String encryptDesCbc(final String src, final String key) throws BaseException { - if (key.length() < 24) { + if (key == null || key.length() < 24) { throw new BaseException(ExpCode.INVALID_KEY_LENGTH); } try { - Key deskey = null; - DESedeKeySpec spec = new DESedeKeySpec(key.getBytes()); - SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede"); - deskey = keyfactory.generateSecret(spec); - - Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding"); - IvParameterSpec ips = new IvParameterSpec(IV.getBytes()); + Key deskey = generateKey(key); + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + // 使用更安全的随机IV + byte[] ivBytes = new byte[8]; + new SecureRandom().nextBytes(ivBytes); + IvParameterSpec ips = new IvParameterSpec(ivBytes); cipher.init(Cipher.ENCRYPT_MODE, deskey, ips); - byte[] encryptData = cipher.doFinal(src.getBytes(ENCODING)); - return Base64.encodeBase64URLSafeString(encryptData); + byte[] encryptData = cipher.doFinal(src.getBytes(CHARSET)); + + // 将IV与加密数据一起返回 + byte[] result = new byte[ivBytes.length + encryptData.length]; + System.arraycopy(ivBytes, 0, result, 0, ivBytes.length); + System.arraycopy(encryptData, 0, result, ivBytes.length, encryptData.length); + + return Base64.encodeBase64URLSafeString(result); } catch (Exception e) { throw new BaseException(e, ExpCode.ENCRYPTION_FAILED); } @@ -59,49 +70,70 @@ public class DES3 { * DESCBC解密 * * @param src 数据源 - * @param key 密钥 + * @param key 密钥,长度至少24位 * @return 返回解密后的原始数据 - * @throws BaseException + * @throws BaseException 解密异常 */ public static String decryptDesCbc(final String src, final String key) throws BaseException { - if (key.length() < 24) { + if (key == null || key.length() < 24) { throw new BaseException(ExpCode.INVALID_KEY_LENGTH); } try { - Key deskey = null; - DESedeKeySpec spec = new DESedeKeySpec(key.getBytes()); - SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede"); - deskey = keyfactory.generateSecret(spec); - Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding"); - IvParameterSpec ips = new IvParameterSpec(IV.getBytes()); + Key deskey = generateKey(key); + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + + // 从加密数据中提取IV + byte[] srcBytes = Base64.decodeBase64(src); + byte[] ivBytes = new byte[8]; + byte[] encryptedData = new byte[srcBytes.length - 8]; + System.arraycopy(srcBytes, 0, ivBytes, 0, 8); + System.arraycopy(srcBytes, 8, encryptedData, 0, encryptedData.length); + + IvParameterSpec ips = new IvParameterSpec(ivBytes); cipher.init(Cipher.DECRYPT_MODE, deskey, ips); - byte[] decryptData = cipher.doFinal(Base64.decodeBase64(src)); - - return new String(decryptData, ENCODING); + byte[] decryptData = cipher.doFinal(encryptedData); + return new String(decryptData, CHARSET); } catch (Exception e) { throw new BaseException(e, ExpCode.DECRYPTION_PARAM_FAILED, src); } } + /** + * 生成3DES密钥 + * + * @param key 密钥字符串 + * @return Key对象 + * @throws InvalidKeyException 密钥生成异常 + * @throws NoSuchAlgorithmException 密钥生成异常 + * @throws InvalidKeySpecException 密钥生成异常 + */ + private static Key generateKey(String key) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException { + DESedeKeySpec spec = new DESedeKeySpec(key.getBytes(CHARSET)); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM); + return keyFactory.generateSecret(spec); + } + /** * 填充,不是8的倍数会填充成8的倍数 * - * @param str - * @return + * @param str 待填充的字符串 + * @return 填充后的字符串 */ public static String padding(String str) { - byte[] oldByteArray; - oldByteArray = str.getBytes(StandardCharsets.UTF_8); - int numberToPad = 8 - oldByteArray.length % 8; + byte[] oldByteArray = str.getBytes(CHARSET); + int len = 8; + int numberToPad = len - oldByteArray.length % len; + if (numberToPad == len) { + // 如果已经是8的倍数,则不填充 + numberToPad = 0; + } byte[] newByteArray = new byte[oldByteArray.length + numberToPad]; - System.arraycopy(oldByteArray, 0, newByteArray, 0, - oldByteArray.length); + System.arraycopy(oldByteArray, 0, newByteArray, 0, oldByteArray.length); for (int i = oldByteArray.length; i < newByteArray.length; ++i) { newByteArray[i] = 0; } - return new String(newByteArray, StandardCharsets.UTF_8); + return new String(newByteArray, CHARSET); } -} - +} \ No newline at end of file diff --git a/src/main/java/com/yexuejc/base/encrypt/RSA.java b/src/main/java/com/yexuejc/base/encrypt/RSA.java index fd789cd..3bd50fc 100644 --- a/src/main/java/com/yexuejc/base/encrypt/RSA.java +++ b/src/main/java/com/yexuejc/base/encrypt/RSA.java @@ -37,20 +37,25 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; * RSA加解密 配置模式 * * @ClassName: RSA - * @Description: + * @Description: 提供RSA加密、解密、签名和验证功能的工具类 * @author: maxf * @date: 2018/5/15 14:39 */ public class RSA { + /** 私钥文件名常量 */ + private static final String PRIVATE_KEY_FILE = "private.key"; + /** 公钥文件名常量 */ + private static final String PUBLIC_KEY_FILE = "public.key"; + public static RSA builder() { - return RSA.Instace.rsa; + return Instance.RSA; } - private static class Instace { - private static RSA rsa = new RSA(); + private static class Instance { + private static final RSA RSA = new RSA(); } - private static final System.Logger log = System.getLogger(RSA.class.getName()); + private static final System.Logger LOGGER = System.getLogger(RSA.class.getName()); public static final Charset CHARSET = StandardCharsets.UTF_8; /** * 算法名称 @@ -66,7 +71,7 @@ public class RSA { *

使用方式:

*

*/ - public String algorithm = "RSA"; + public volatile String algorithm = "RSA"; /** * 加密方式 *
示例:
@@ -95,12 +100,12 @@ public class RSA { *
*

AES的(算法/模式/填充)组合 参照 {@link ALGORITHM}

*/ - public String transformation = "RSA"; + public volatile String transformation = "RSA"; /** * 是否每次改变加密结果 * 只针对于 transformation = "RSA"有效 */ - public boolean isChangeSign = true; + public volatile boolean isChangeSign = true; /** * 是否使用 Base64URL 方式加密 默认正常加密 *
@@ -121,7 +126,7 @@ public class RSA {
      *     };
      * 
*/ - public boolean encodeBase64URLSafe = false; + public volatile boolean encodeBase64URLSafe = false; /** * 签名算法 */ @@ -168,18 +173,23 @@ public class RSA { * * @param keySize 生成长度 * @param base64URLSafe 是否生成 base64URL 格式的密钥:默认false - * @return + * @return 包含公钥和私钥的Map,key为"publicKey"和"privateKey" */ public Map initKeys(int keySize, boolean base64URLSafe) { - encodeBase64URLSafe = base64URLSafe; - return initKeys(keySize); + boolean originalEncodeBase64URLSafe = this.encodeBase64URLSafe; + try { + this.encodeBase64URLSafe = base64URLSafe; + return initKeys(keySize); + } finally { + this.encodeBase64URLSafe = originalEncodeBase64URLSafe; + } } /** * 生成密钥对 * * @param keySize 生成长度 - * @return + * @return 包含公钥和私钥的Map,key为"publicKey"和"privateKey" */ public Map initKeys(int keySize) { //为RSA算法创建一个KeyPairGenerator对象 @@ -220,13 +230,13 @@ public class RSA { *

* * @param filePath 密钥文件路径 - * @throws BaseException + * @throws BaseException 生成密钥文件异常 */ public void initKey4File(String filePath) throws BaseException { Map keys = initKeys(2048, false); try { - Files.write(Paths.get(filePath, "private.key"), StrUtil.chunkString(keys.get("privateKey"), 64).getBytes(CHARSET)); - Files.write(Paths.get(filePath, "public.key"), StrUtil.chunkString(keys.get("publicKey"), 64).getBytes(CHARSET)); + Files.writeString(Paths.get(filePath, PRIVATE_KEY_FILE), StrUtil.chunkString(keys.get("privateKey"), 64),CHARSET); + Files.writeString(Paths.get(filePath, PUBLIC_KEY_FILE), StrUtil.chunkString(keys.get("publicKey"), 64),CHARSET); } catch (IOException e) { throw new BaseException(e, ExpCode.CREATE_KEY_FILE_FAILED); } @@ -236,7 +246,9 @@ public class RSA { * 得到公钥 * * @param publicKey 密钥字符串(经过base64编码) - * @throws Exception + * @return RSAPublicKey对象 + * @throws NoSuchAlgorithmException 算法不存在异常 + * @throws InvalidKeySpecException 无效的密钥规范异常 */ public RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException { //通过X509编码的Key指令获得公钥对象 @@ -249,7 +261,9 @@ public class RSA { * 得到私钥 * * @param privateKey 密钥字符串(经过base64编码) - * @throws Exception + * @return RSAPrivateKey对象 + * @throws NoSuchAlgorithmException 算法不存在异常 + * @throws InvalidKeySpecException 无效的密钥规范异常 */ public RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException { //通过PKCS#8编码的Key指令获得私钥对象 @@ -264,11 +278,17 @@ public class RSA { * @param data 加密原串数据 * @param publicKey 公钥 * @param base64URLSafe 是否生成 base64URL 格式的密钥:默认false - * @return + * @return 加密后的数据 + * @throws BaseException 加密异常 */ public String publicEncrypt(String data, RSAPublicKey publicKey, boolean base64URLSafe) throws BaseException { - encodeBase64URLSafe = base64URLSafe; - return publicEncrypt(data, publicKey); + boolean originalEncodeBase64URLSafe = this.encodeBase64URLSafe; + try { + this.encodeBase64URLSafe = base64URLSafe; + return publicEncrypt(data, publicKey); + } finally { + this.encodeBase64URLSafe = originalEncodeBase64URLSafe; + } } /** @@ -276,7 +296,8 @@ public class RSA { * * @param data 加密原串数据 * @param publicKey 公钥 - * @return + * @return 加密后的数据 + * @throws BaseException 加密异常 */ public String publicEncrypt(String data, RSAPublicKey publicKey) throws BaseException { return encrypt(data, publicKey); @@ -285,9 +306,10 @@ public class RSA { /** * 私钥解密 * - * @param data - * @param privateKey - * @return + * @param data 待解密数据 + * @param privateKey 私钥 + * @return 解密后的数据 + * @throws BaseException 解密异常 */ public String privateDecrypt(String data, RSAPrivateKey privateKey) throws BaseException { return decrypt(data, privateKey); @@ -297,69 +319,92 @@ public class RSA { * 私钥加密 * * @param data 加密原串数据 - * @param privateKey 公钥 + * @param privateKey 私钥 * @param base64URLSafe 是否生成 base64URL 格式的密钥:默认false - * @return + * @return 加密后的数据 + * @throws BaseException 加密异常 */ public String privateEncrypt(String data, RSAPrivateKey privateKey, boolean base64URLSafe) throws BaseException { - encodeBase64URLSafe = base64URLSafe; - return privateEncrypt(data, privateKey); + boolean originalEncodeBase64URLSafe = this.encodeBase64URLSafe; + try { + this.encodeBase64URLSafe = base64URLSafe; + return privateEncrypt(data, privateKey); + } finally { + this.encodeBase64URLSafe = originalEncodeBase64URLSafe; + } } /** * 私钥加密 * * @param data 加密原串数据 - * @param privateKey 公钥 - * @return + * @param privateKey 私钥 + * @return 加密后的数据 + * @throws BaseException 加密异常 */ public String privateEncrypt(String data, RSAPrivateKey privateKey) throws BaseException { return encrypt(data, privateKey); } /** - * 使用密钥加密,支持公玥,私玥 + * 使用密钥加密,支持公钥,私钥 * * @param data 待加密的数据 * @param key 公钥或私钥 * @return 加密后的数据 - * @throws BaseException + * @throws BaseException 加密异常 */ private String encrypt(String data, Key key) throws BaseException { try { - // @formatter:off Cipher cipher = getCipher(); cipher.init(Cipher.ENCRYPT_MODE, key); + int keyLength = getKeyLength(key); if (encodeBase64URLSafe) { - return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), - key instanceof RSAPublicKey ? ((RSAPublicKey) key).getModulus().bitLength() : - ((RSAPrivateKey) key).getModulus().bitLength())); + return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), keyLength)); } else { - return Base64.encodeBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), - key instanceof RSAPublicKey ?((RSAPublicKey) key).getModulus().bitLength() : - ((RSAPrivateKey) key).getModulus().bitLength())); + return Base64.encodeBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), keyLength)); } - // @formatter:on } catch (Exception e) { throw new BaseException(e, ExpCode.ENCRYPTION_PARAM_FAILED, data); } } + /** + * 使用密钥解密 + * + * @param data 待解密的数据 + * @param key 公钥或私钥 + * @return 解密后的数据 + * @throws BaseException 解密异常 + */ private String decrypt(String data, Key key) throws BaseException { try { Cipher cipher = getCipher(); cipher.init(Cipher.DECRYPT_MODE, key); - // @formatter:off - return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), - key instanceof RSAPublicKey ?((RSAPublicKey) key).getModulus().bitLength() : - ((RSAPrivateKey) key).getModulus().bitLength()) - , CHARSET); - // @formatter:on + int keyLength = getKeyLength(key); + return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), keyLength), CHARSET); } catch (Exception e) { throw new BaseException(e, ExpCode.DECRYPTION_PARAM_FAILED, data); } } + /** + * 获取密钥长度 + * + * @param key 密钥 + * @return 密钥长度 + * @throws IllegalArgumentException 当密钥类型不是RSA公钥或私钥时抛出 + */ + private int getKeyLength(Key key) { + // 根据密钥类型获取对应的模数位长度 + if (key instanceof RSAPublicKey publicKey) { + return publicKey.getModulus().bitLength(); + } else if(key instanceof RSAPrivateKey privateKey) { + return privateKey.getModulus().bitLength(); + } else { + throw new IllegalArgumentException("密钥类型不支持: " + key.getClass().getName()); + } + } /** * 公钥解密 @@ -367,6 +412,7 @@ public class RSA { * @param data 待解密数据 * @param publicKey 公钥 * @return 解密后的数据 + * @throws BaseException 解密异常 */ public String publicDecrypt(String data, RSAPublicKey publicKey) throws BaseException { @@ -376,16 +422,17 @@ public class RSA { /** * 获取 Cipher * - * @return - * @throws NoSuchPaddingException - * @throws NoSuchAlgorithmException - * @throws NoSuchProviderException + * @return Cipher对象 + * @throws NoSuchPaddingException 无此填充异常 + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws NoSuchProviderException 无此提供者异常 */ private Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException { Cipher cipher; if (transformation.startsWith("RSA") && isChangeSign) { // 每次改变加密结果 if (ALGORITHM.RSA_ECB_SHA3_256.code.equals(transformation)) { + Security.addProvider(new BouncyCastleProvider()); cipher = Cipher.getInstance(transformation, "BC"); } else { cipher = Cipher.getInstance(transformation); @@ -398,6 +445,16 @@ public class RSA { return cipher; } + /** + * RSA分段编解码 + * + * @param cipher 加密器 + * @param opmode 操作模式(加密/解密) + * @param datas 待处理数据 + * @param keySize 密钥长度 + * @return 处理后的数据 + * @throws BaseException 编解码异常 + */ private byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) throws BaseException { int maxBlock = 0; if (opmode == Cipher.DECRYPT_MODE) { @@ -405,6 +462,11 @@ public class RSA { } else { maxBlock = keySize / 8 - 11; } + + if (maxBlock <= 0) { + throw new BaseException("密钥大小无效: " + keySize, ExpCode.DATA_DECODE_PARAM_FAILED, keySize); + } + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { int offSet = 0; byte[] buff; @@ -435,12 +497,17 @@ public class RSA { * @param plaintext 签名字符串 * @param privateKey 签名私钥 * @param base64URLSafe 是否生成 base64URL 格式的密钥:默认false - * @return - * @throws BaseException + * @return 签名串 + * @throws BaseException 签名异常 */ public String sign(String plaintext, RSAPrivateKey privateKey, boolean base64URLSafe) throws BaseException { - encodeBase64URLSafe = base64URLSafe; - return sign(plaintext, privateKey); + boolean originalEncodeBase64URLSafe = this.encodeBase64URLSafe; + try { + this.encodeBase64URLSafe = base64URLSafe; + return sign(plaintext, privateKey); + } finally { + this.encodeBase64URLSafe = originalEncodeBase64URLSafe; + } } /** @@ -452,7 +519,7 @@ public class RSA { * @param plaintext 签名字符串 * @param privateKey 签名私钥 * @return 签名串 - * @throws BaseException + * @throws BaseException 签名异常 */ public String sign(String plaintext, RSAPrivateKey privateKey) throws BaseException { try { @@ -478,7 +545,7 @@ public class RSA { * @param signStr 签名串 * @param publicKey 公钥 * @return true:校验成功 / false:校验失败 - * @throws BaseException + * @throws BaseException 校验异常 */ public boolean verify(String plaintext, String signStr, RSAPublicKey publicKey) throws BaseException { try { @@ -517,20 +584,30 @@ public class RSA { // @formatter:on String sign = respHeader.getSignature(); if (sign.contains(SymbolicConsts.COMMA)) { - sign = Arrays.stream(sign.split(SymbolicConsts.COMMA)) - .map(s -> s.split(SymbolicConsts.EQUAL)) - .filter(subSplit -> subSplit.length > 1 && RequestHeader.SIGNATURE.equalsIgnoreCase(subSplit[0])) - .map(subSplit -> subSplit[1]) - .findFirst() - .orElse(sign); + sign = extractSignature(sign); } - log.log(System.Logger.Level.DEBUG, "签名内容:=====\n{0}\n=====", signContent); + LOGGER.log(System.Logger.Level.DEBUG, "签名内容:=====\n{0}\n=====", signContent); return verify(signContent, sign, RSA2.getPublicKey(publicKeyPath)); } catch (Exception e) { throw new BaseException(e, ExpCode.VERIFY_FAILED); } } + /** + * 从复合签名字符串中提取签名值 + * + * @param sign 复合签名字符串 + * @return 提取的签名值 + */ + private String extractSignature(String sign) { + // 按逗号分割签名字符串,然后查找以SIGNATURE开头的部分 + return Arrays.stream(sign.split(SymbolicConsts.COMMA)) + .map(s -> s.split(SymbolicConsts.EQUAL)) + .filter(subSplit -> subSplit.length > 1 && RequestHeader.SIGNATURE.equalsIgnoreCase(subSplit[0])) + .map(subSplit -> subSplit[1]) + .findFirst() + .orElse(sign); + } /** * 签名 @@ -556,13 +633,13 @@ public class RSA { .replace("{reqTime}", requestHeader.getReqTime()) .replace("{body}", data); // @formatter:on - log.log(System.Logger.Level.DEBUG, "签名内容:=====\n{0}\n=====", signContent); + LOGGER.log(System.Logger.Level.DEBUG, "签名内容:=====\n{0}\n=====", signContent); return sign(signContent, RSA2.getPrivateKeyFromPKCS1(privateKeyPath), true); } catch (Exception e) { - throw new BaseException(e, "签名时发生异常"); + throw new BaseException(e, ExpCode.SIGN_FAILED); } } private RSA() { } -} +} \ No newline at end of file diff --git a/src/main/java/com/yexuejc/base/encrypt/RSA2.java b/src/main/java/com/yexuejc/base/encrypt/RSA2.java index 343cf6e..cbe20a7 100644 --- a/src/main/java/com/yexuejc/base/encrypt/RSA2.java +++ b/src/main/java/com/yexuejc/base/encrypt/RSA2.java @@ -42,7 +42,7 @@ import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; * 依赖 {@link RSA} * * @ClassName: RSA2 - * @Description: + * @Description: 提供基于证书的RSA加解密功能,支持多种证书格式(JKS、PKCS12、PKCS8等) * @author: maxf * @date: 2018/5/15 14:37 */ @@ -65,7 +65,9 @@ public class RSA2 { * 得到公钥 * * @param filepath 密钥文件路径 - * @throws Exception + * @return RSAPublicKey 公钥对象 + * @throws CertificateException 证书异常 + * @throws FileNotFoundException 文件未找到异常 */ public static RSAPublicKey getPublicKey(String filepath) throws CertificateException, FileNotFoundException { try (FileInputStream pubKeyIn = new FileInputStream(filepath)) { @@ -92,8 +94,8 @@ public class RSA2 { * 得到公钥 * * @param pubKeyIn 密钥文件流 - * @return - * @throws CertificateException + * @return RSAPublicKey 公钥对象 + * @throws CertificateException 证书异常 */ public static RSAPublicKey getPublicKey(InputStream pubKeyIn) throws CertificateException { //通过证书,获取公钥 @@ -108,8 +110,8 @@ public class RSA2 { * @param pubKeyPath 密钥文件路径 * @param alias 别名 * @param password 密码 - * @return RSAPublicKey - * @throws BaseException + * @return RSAPublicKey 公钥对象 + * @throws BaseException 基础异常 */ public static RSAPublicKey getPublicKeyFromPKCS12(String pubKeyPath, String alias, String password) throws BaseException { try (FileInputStream fis = new FileInputStream(pubKeyPath)) { @@ -125,8 +127,8 @@ public class RSA2 { * @param pubKeyIn 密钥文件流 * @param alias 别名 * @param password 密码 - * @return RSAPublicKey - * @throws BaseException + * @return RSAPublicKey 公钥对象 + * @throws BaseException 基础异常 */ public static RSAPublicKey getPublicKeyFromPKCS12(InputStream pubKeyIn, String alias, String password) throws BaseException { try { @@ -143,8 +145,8 @@ public class RSA2 { * 从PEM格式的公钥文件中提取公钥 * * @param pemContent PEM格式的内容 - * @return RSAPublicKey - * @throws CertificateException + * @return RSAPublicKey 公钥对象 + * @throws CertificateException 证书异常 */ private static RSAPublicKey getPublicKey4Pem(String pemContent) throws CertificateException { try { @@ -185,8 +187,8 @@ public class RSA2 { * @param filepath 私钥路径 * @param alias 证书别名 * @param password 证书密码 - * @return - * @throws BaseException + * @return RSAPrivateKey 私钥对象 + * @throws BaseException 基础异常 */ public static RSAPrivateKey getPrivateKey(String filepath, String alias, String password) throws BaseException { return getPrivateKey(filepath, alias, password, KEY_JKS); @@ -198,8 +200,8 @@ public class RSA2 { * @param priKeyIn 私钥文件流 * @param alias 证书别名 * @param password 证书密码 - * @return - * @throws BaseException + * @return RSAPrivateKey 私钥对象 + * @throws BaseException 基础异常 */ public static RSAPrivateKey getPrivateKey(InputStream priKeyIn, String alias, String password) throws BaseException { return getPrivateKey(priKeyIn, alias, password, KEY_JKS); @@ -211,8 +213,8 @@ public class RSA2 { * @param filepath 私钥路径 * @param alias 证书别名 可空 * @param password 证书密码 - * @return - * @throws BaseException + * @return RSAPrivateKey 私钥对象 + * @throws BaseException 基础异常 */ public static RSAPrivateKey getPrivateKeyFromPKCS12(String filepath, String alias, String password) throws BaseException { return getPrivateKey(filepath, alias, password, KEY_PKCS12); @@ -224,8 +226,8 @@ public class RSA2 { * @param priKeyIn 私钥文件流 * @param alias 证书别名 可空 * @param password 证书密码 - * @return - * @throws BaseException + * @return RSAPrivateKey 私钥对象 + * @throws BaseException 基础异常 */ public static RSAPrivateKey getPrivateKeyFromPKCS12(InputStream priKeyIn, String alias, String password) throws BaseException { return getPrivateKey(priKeyIn, alias, password, KEY_PKCS12); @@ -235,8 +237,8 @@ public class RSA2 { * 读取PKCS8格式的key(私钥)pem格式 * * @param filepath 私钥路径 - * @return - * @throws BaseException + * @return RSAPrivateKey 私钥对象 + * @throws BaseException 基础异常 */ public static RSAPrivateKey getPrivateKeyFromPKCS8(String filepath, String password) throws BaseException { return getPrivateKey(filepath, null, password, KEY_PKCS8); @@ -246,8 +248,8 @@ public class RSA2 { * 读取PKCS8格式的key(私钥)pem格式 * * @param priKeyIn 私钥文件流 - * @return - * @throws BaseException + * @return RSAPrivateKey 私钥对象 + * @throws BaseException 基础异常 */ public static RSAPrivateKey getPrivateKeyFromPKCS8(InputStream priKeyIn, String password) throws BaseException { return getPrivateKey(priKeyIn, null, password, KEY_PKCS8); @@ -257,8 +259,8 @@ public class RSA2 { * 读取PKCS8格式的key(私钥)pem格式 * * @param filepath 私钥路径 - * @return - * @throws BaseException + * @return RSAPrivateKey 私钥对象 + * @throws BaseException 基础异常 */ public static RSAPrivateKey getPrivateKeyFromPKCS1(String filepath) throws BaseException { return getPrivateKey(filepath, null, null, KEY_PKCS1); @@ -268,8 +270,8 @@ public class RSA2 { * 读取PKCS8格式的key(私钥)pem格式 * * @param priKeyIn 私钥文件流 - * @return - * @throws BaseException + * @return RSAPrivateKey 私钥对象 + * @throws BaseException 基础异常 */ public static RSAPrivateKey getPrivateKeyFromPKCS1(InputStream priKeyIn) throws BaseException { return getPrivateKey(priKeyIn, null, null, KEY_PKCS1); @@ -282,8 +284,8 @@ public class RSA2 { * @param alias 证书别名 可空 * @param password 证书密码 * @param type 证书格式 - * @return - * @throws BaseException + * @return RSAPrivateKey 私钥对象 + * @throws BaseException 基础异常 */ public static RSAPrivateKey getPrivateKey(String filepath, String alias, String password, String type) throws BaseException { try (FileInputStream fileInputStream = new FileInputStream(filepath)) { @@ -300,8 +302,8 @@ public class RSA2 { * @param alias 证书别名 可空 * @param password 证书密码 * @param type 证书格式 - * @return - * @throws BaseException + * @return RSAPrivateKey 私钥对象 + * @throws BaseException 基础异常 */ public static RSAPrivateKey getPrivateKey(InputStream priKeyIn, String alias, String password, String type) throws BaseException { try { @@ -349,8 +351,8 @@ public class RSA2 { * * @param pemContent PEM格式的加密私钥内容 * @param password 密码 - * @return RSAPrivateKey - * @throws Exception + * @return RSAPrivateKey 私钥对象 + * @throws Exception 异常 */ private static RSAPrivateKey getPrivateKeyByPKCS8(String pemContent, String password) throws Exception { if (Security.getProvider(BOUNCY_CASTLE_PROVIDER_KEY) == null) { @@ -361,25 +363,24 @@ public class RSA2 { // 处理PKCS#1格式的RSA私钥 if (object instanceof org.bouncycastle.asn1.pkcs.RSAPrivateKey) { - JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER_KEY); PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(object); return (RSAPrivateKey) converter.getPrivateKey(privateKeyInfo); } else if (object instanceof PEMKeyPair) { // 处理PKCS#1格式的RSA私钥 - JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER_KEY); return (RSAPrivateKey) converter.getPrivateKey(((PEMKeyPair) object).getPrivateKeyInfo()); - } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) { + } else if (object instanceof PKCS8EncryptedPrivateKeyInfo encryptedInfo) { // 处理加密的PKCS#8私钥 - PKCS8EncryptedPrivateKeyInfo encryptedInfo = (PKCS8EncryptedPrivateKeyInfo) object; InputDecryptorProvider provider = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(BOUNCY_CASTLE_PROVIDER_KEY) .build(password.toCharArray()); PrivateKeyInfo privateKeyInfo = encryptedInfo.decryptPrivateKeyInfo(provider); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER_KEY); return (RSAPrivateKey) converter.getPrivateKey(privateKeyInfo); } else if (object instanceof PrivateKeyInfo) { // 处理未加密的PKCS#8私钥 - JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BOUNCY_CASTLE_PROVIDER_KEY); return (RSAPrivateKey) converter.getPrivateKey((PrivateKeyInfo) object); } throw new IllegalArgumentException("不支持的PEM格式"); @@ -395,6 +396,7 @@ public class RSA2 { * @param outPath 证书输出文件路径 * @param oPwd 原证书密码 * @param nPwd 新证书密码(为空同原证书密码一致) + * @throws BaseException 基础异常 */ public static void cover2Pfx(String inPath, String outPath, String oPwd, String nPwd) throws BaseException { try (FileInputStream fis = new FileInputStream(inPath); FileOutputStream out = new FileOutputStream(outPath)) { @@ -414,6 +416,7 @@ public class RSA2 { * @param out 证书输出文件流[自行关闭->out.close()] * @param oPwd 原证书密码 * @param nPwd 新证书密码(为空同原证书密码一致) + * @throws BaseException 基础异常 */ public static void cover2Pfx(FileInputStream fis, FileOutputStream out, char[] oPwd, char[] nPwd) throws BaseException { try { @@ -431,6 +434,7 @@ public class RSA2 { * @param outPath 证书输出文件路径 * @param oPwd 原证书密码 * @param nPwd 新证书密码(为空同原证书密码一致) + * @throws BaseException 基础异常 */ public static void cover2keyStore(String inPath, String outPath, String oPwd, String nPwd) throws BaseException { try (FileInputStream fis = new FileInputStream(inPath); FileOutputStream out = new FileOutputStream(outPath)) { @@ -451,6 +455,7 @@ public class RSA2 { * @param out 证书输出文件流[自行关闭->out.close()] * @param oPwd 原证书密码 * @param nPwd 新证书密码(为空同原证书密码一致) + * @throws BaseException 基础异常 */ public static void cover2keyStore(FileInputStream fis, FileOutputStream out, char[] oPwd, char[] nPwd) throws BaseException { try { @@ -470,11 +475,11 @@ public class RSA2 { * @param nPwd 新证书密码(为空同原证书密码一致) * @param inputKeyStore 输入格式 * @param type 目标类型 - * @throws IOException - * @throws NoSuchAlgorithmException - * @throws CertificateException - * @throws KeyStoreException - * @throws UnrecoverableKeyException + * @throws IOException IO异常 + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws CertificateException 证书异常 + * @throws KeyStoreException 密钥库异常 + * @throws UnrecoverableKeyException 无法恢复密钥异常 */ public static void cover(FileInputStream fis, FileOutputStream out, char[] oPwd, char[] nPwd, KeyStore inputKeyStore, String type) throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException, UnrecoverableKeyException { inputKeyStore.load(fis, oPwd); diff --git a/src/main/java/com/yexuejc/base/encrypt/RSACoder.java b/src/main/java/com/yexuejc/base/encrypt/RSACoder.java index f77f013..f0e116b 100644 --- a/src/main/java/com/yexuejc/base/encrypt/RSACoder.java +++ b/src/main/java/com/yexuejc/base/encrypt/RSACoder.java @@ -1,19 +1,26 @@ package com.yexuejc.base.encrypt; -import org.apache.commons.codec.binary.Base64; - -import javax.crypto.Cipher; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +import org.apache.commons.codec.binary.Base64; /** * RSA 加解密 工具模式 * * @author maxf * @ClassName RSACoder - * @Description + * @Description RSA加解密工具类,提供基于公钥和私钥的加密解密功能 * @date 2018/9/3 16:13 */ public class RSACoder { @@ -21,175 +28,166 @@ public class RSACoder { public static final String KEY_ALGORITHM = "RSA"; /** - * 解密
- * 用公钥解密 + * 使用公钥解密数据 * - * @param data - * @param key - * @return - * @throws Exception + * @param data 待解密的数据(BASE64编码的字符串) + * @param key 公钥(BASE64编码) + * @return 解密后的字节数组 + * @throws Exception 解密过程中可能出现的异常 */ - public static byte[] decryptByPublic(String data, String key) - throws Exception { + public static byte[] decryptByPublic(String data, String key) throws Exception { return getDataByPublicKey(data, key, Cipher.DECRYPT_MODE); } /** - * 解密
- * 用私钥解密 + * 使用私钥解密数据 * - * @param data - * @param key - * @return - * @throws Exception + * @param data 待解密的数据(BASE64编码的字符串) + * @param key 私钥(BASE64编码) + * @return 解密后的字节数组 + * @throws Exception 解密过程中可能出现的异常 */ - public static byte[] decryptByPrivateKey(String data, String key) - throws Exception { + public static byte[] decryptByPrivateKey(String data, String key) throws Exception { return getDataByPrivateKey(data, key, Cipher.DECRYPT_MODE); } /** - * 加密
- * 用公钥加密 + * 使用公钥加密数据 * - * @param data - * @param key - * @return - * @throws Exception + * @param data 待加密的数据(BASE64编码的字符串) + * @param key 公钥(BASE64编码) + * @return 加密后的字节数组 + * @throws Exception 加密过程中可能出现的异常 */ - public static byte[] encryptByPublicKey(String data, String key) - throws Exception { + public static byte[] encryptByPublicKey(String data, String key) throws Exception { return getDataByPublicKey(data, key, Cipher.ENCRYPT_MODE); } /** - * 加密
- * 用私钥加密 + * 使用私钥加密数据 * - * @param data - * @param key - * @return - * @throws Exception + * @param data 待加密的数据(BASE64编码的字符串) + * @param key 私钥(BASE64编码) + * @return 加密后的字节数组 + * @throws Exception 加密过程中可能出现的异常 */ - public static byte[] encryptByPrivateKey(String data, String key) - throws Exception { + public static byte[] encryptByPrivateKey(String data, String key) throws Exception { return getDataByPrivateKey(data, key, Cipher.ENCRYPT_MODE); } /** - * 解密
- * 用公钥解密 + * 使用公钥解密数据 * - * @param data - * @param key - * @return - * @throws Exception + * @param data 待解密的字节数组 + * @param key 公钥(BASE64编码) + * @return 解密后的字节数组 + * @throws Exception 解密过程中可能出现的异常 */ - public static byte[] decryptByPublic(byte[] data, String key) - throws Exception { + public static byte[] decryptByPublic(byte[] data, String key) throws Exception { return getDataByPublicKey(data, key, Cipher.DECRYPT_MODE); } /** - * 解密
- * 用私钥解密 + * 使用私钥解密数据 * - * @param data - * @param key - * @return - * @throws Exception + * @param data 待解密的字节数组 + * @param key 私钥(BASE64编码) + * @return 解密后的字节数组 + * @throws Exception 解密过程中可能出现的异常 */ - public static byte[] decryptByPrivateKey(byte[] data, String key) - throws Exception { + public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception { return getDataByPrivateKey(data, key, Cipher.DECRYPT_MODE); } /** - * 加密
- * 用公钥加密 + * 使用公钥加密数据 * - * @param data - * @param key - * @return - * @throws Exception + * @param data 待加密的字节数组 + * @param key 公钥(BASE64编码) + * @return 加密后的字节数组 + * @throws Exception 加密过程中可能出现的异常 */ - public static byte[] encryptByPublicKey(byte[] data, String key) - throws Exception { + public static byte[] encryptByPublicKey(byte[] data, String key) throws Exception { return getDataByPublicKey(data, key, Cipher.ENCRYPT_MODE); } /** - * 加密
- * 用私钥加密 + * 使用私钥加密数据 * - * @param data - * @param key - * @return - * @throws Exception + * @param data 待加密的字节数组 + * @param key 私钥(BASE64编码) + * @return 加密后的字节数组 + * @throws Exception 加密过程中可能出现的异常 */ - public static byte[] encryptByPrivateKey(byte[] data, String key) - throws Exception { + public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception { return getDataByPrivateKey(data, key, Cipher.ENCRYPT_MODE); } /** * 通过公钥获得加解密数据 * - * @param data String - * @param key String - * @param mode int - * @return + * @param data 待处理的数据(BASE64编码的字符串) + * @param key 公钥(BASE64编码) + * @param mode 加密或解密模式(Cipher.ENCRYPT_MODE或Cipher.DECRYPT_MODE) + * @return 处理后的字节数组 + * @throws Exception 处理过程中可能出现的异常 */ - public static byte[] getDataByPublicKey(String data, String key, int mode) - throws Exception { + public static byte[] getDataByPublicKey(String data, String key, int mode) throws Exception { return getDataByPublicKey(data.getBytes(), key, mode); } /** * 通过私钥获得加解密数据 * - * @param data String - * @param key String - * @param mode 加密或解密 - * @return + * @param data 待处理的数据(BASE64编码的字符串) + * @param key 私钥(BASE64编码) + * @param mode 加密或解密模式(Cipher.ENCRYPT_MODE或Cipher.DECRYPT_MODE) + * @return 处理后的字节数组 + * @throws Exception 处理过程中可能出现的异常 */ - public static byte[] getDataByPrivateKey(String data, String key, int mode) - throws Exception { + public static byte[] getDataByPrivateKey(String data, String key, int mode) throws Exception { return getDataByPrivateKey(data.getBytes(), key, mode); } /** * 通过公钥获得加解密数据 * - * @param data String - * @param key String - * @param mode int - * @return + * @param data 待处理的字节数组 + * @param key 公钥(BASE64编码) + * @param mode 加密或解密模式(Cipher.ENCRYPT_MODE或Cipher.DECRYPT_MODE) + * @return 处理后的字节数组 + * @throws Exception 处理过程中可能出现的异常 */ - public static byte[] getDataByPublicKey(byte[] data, String key, int mode) - throws Exception { - // 取得私钥 - X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(key.getBytes("UTF-8"))); + public static byte[] getDataByPublicKey(byte[] data, String key, int mode) throws Exception { + // 取得公钥 + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(key.getBytes(StandardCharsets.UTF_8))); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - Key privateKey = keyFactory.generatePublic(x509KeySpec); + Key publicKey = keyFactory.generatePublic(x509KeySpec); // 对数据进行加密或解密 Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(mode, privateKey); + cipher.init(mode, publicKey); return cipher.doFinal(data); } /** * 通过私钥获得加解密数据 * - * @param data String - * @param key String - * @param mode 加密或解密 - * @return + * @param data 待处理的字节数组 + * @param key 私钥(BASE64编码) + * @param mode 加密或解密模式(Cipher.ENCRYPT_MODE或Cipher.DECRYPT_MODE) + * @return 处理后的字节数组 + * @throws NoSuchAlgorithmException 算法不存在异常 + * @throws InvalidKeySpecException 无效的密钥规范异常 + * @throws NoSuchPaddingException 无此填充异常 + * @throws InvalidKeyException 无效密钥异常 + * @throws IllegalBlockSizeException 非法块大小异常 + * @throws BadPaddingException 坏填充异常 */ - public static byte[] getDataByPrivateKey(byte[] data, String key, int mode) - throws Exception { + public static byte[] getDataByPrivateKey(byte[] data, String key, + int mode) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, + InvalidKeyException, IllegalBlockSizeException, BadPaddingException { // 取得私钥 - PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(key.getBytes("UTF-8"))); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(key.getBytes(StandardCharsets.UTF_8))); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); // 对数据解密 @@ -197,4 +195,4 @@ public class RSACoder { cipher.init(mode, privateKey); return cipher.doFinal(data); } -} +} \ No newline at end of file diff --git a/src/main/java/com/yexuejc/base/exception/BaseException.java b/src/main/java/com/yexuejc/base/exception/BaseException.java index a16b1c8..ede3a8e 100644 --- a/src/main/java/com/yexuejc/base/exception/BaseException.java +++ b/src/main/java/com/yexuejc/base/exception/BaseException.java @@ -1,5 +1,7 @@ package com.yexuejc.base.exception; +import java.io.Serial; + import com.yexuejc.base.constant.ExpCode; import com.yexuejc.base.constant.SymbolicConsts; import com.yexuejc.base.util.MsgUtil; @@ -14,6 +16,7 @@ import com.yexuejc.base.util.StrUtil; public class BaseException extends Exception { /** 序列化 */ + @Serial private static final long serialVersionUID = 1L; /** 异常消息CODE */ protected final String errorCode; @@ -75,8 +78,7 @@ public class BaseException extends Exception { } this.errorCode = errorCode; this.errorMessage = getMessageByCode(errorCode, args); - if (cause instanceof BaseException) { - BaseException comExp = (BaseException) cause; + if (cause instanceof BaseException comExp) { if (StrUtil.isNotEmpty(comExp.errorMessage)) { this.errorMessage = StrUtil.isEmpty(this.errorMessage) ? comExp.errorMessage : this.errorMessage + SymbolicConsts.NEW_LINE + comExp.errorMessage; diff --git a/src/main/java/com/yexuejc/base/file/FileInput.java b/src/main/java/com/yexuejc/base/file/FileInput.java index 5bb000a..29a2c0a 100644 --- a/src/main/java/com/yexuejc/base/file/FileInput.java +++ b/src/main/java/com/yexuejc/base/file/FileInput.java @@ -9,8 +9,10 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.Charset; +import java.util.NoSuchElementException; import java.util.Scanner; import java.util.stream.Collectors; @@ -33,6 +35,10 @@ import org.apache.commons.io.IOUtils; * 12. {@link #read4CharStreams(InputStream, Charset)}
*/ public class FileInput { + private static final System.Logger LOGGER = System.getLogger(FileInput.class.getName()); + private static final int BUFFER_SIZE = 8192; + private static final String IO_EXCEPTION_MSG = "读取流时发生IO异常"; + /** * 读取IO流内容:byte方式 * @@ -42,9 +48,24 @@ public class FileInput { * @throws IOException */ public static String read4Byte(InputStream inputStream, Charset charset) throws IOException { - byte[] bytes = new byte[inputStream.available()]; - inputStream.read(bytes); - return new String(bytes, charset == null ? Charset.defaultCharset() : charset); + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + byte[] buffer = new byte[BUFFER_SIZE]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + baos.write(buffer, 0, bytesRead); + } + return baos.toString(getCharsetName(charset)); + } + } + + /** + * 获取字符集名称 + * + * @param charset 字符集 + * @return 字符集名称 + */ + private static String getCharsetName(Charset charset) { + return (charset == null ? Charset.defaultCharset() : charset).name(); } /** @@ -55,10 +76,13 @@ public class FileInput { * @param lineSeparator 换行方式:默认跟随系统 {@link System#lineSeparator()} * @return */ - public static String read4BufferedReader(InputStream inputStream, Charset charset, String lineSeparator) { - return new BufferedReader( - new InputStreamReader(inputStream, charset == null ? Charset.defaultCharset() : charset) - ).lines().collect(Collectors.joining(lineSeparator == null ? System.lineSeparator() : lineSeparator)); + public static String read4BufferedReader(InputStream inputStream, Charset charset, String lineSeparator) throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset == null ? Charset.defaultCharset() : charset))) { + return reader.lines().collect(Collectors.joining(lineSeparator == null ? System.lineSeparator() : lineSeparator)); + } catch (IOException e) { + LOGGER.log(System.Logger.Level.WARNING, IO_EXCEPTION_MSG, e); + throw e; + } } /** @@ -69,11 +93,13 @@ public class FileInput { * @param lineSeparator 换行方式:默认跟随系统 {@link System#lineSeparator()} * @return */ - public static String read4BufferedReaderParallel(InputStream inputStream, Charset charset, String lineSeparator) { - return new BufferedReader( - new InputStreamReader(inputStream, charset == null ? Charset.defaultCharset() : charset) - ).lines().parallel() - .collect(Collectors.joining(lineSeparator == null ? System.lineSeparator() : lineSeparator)); + public static String read4BufferedReaderParallel(InputStream inputStream, Charset charset, String lineSeparator) throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset == null ? Charset.defaultCharset() : charset))) { + return reader.lines().parallel().collect(Collectors.joining(lineSeparator == null ? System.lineSeparator() : lineSeparator)); + } catch (IOException e) { + LOGGER.log(System.Logger.Level.WARNING, IO_EXCEPTION_MSG, e); + throw e; + } } /** @@ -83,9 +109,9 @@ public class FileInput { * @return */ public static String read4ScannerA(InputStream inputStream) { - Scanner s = new Scanner(inputStream).useDelimiter("\\A"); - String str = s.hasNext() ? s.next() : ""; - return str; + try (Scanner s = new Scanner(inputStream).useDelimiter("\\A")) { + return s.hasNext() ? s.next() : ""; + } } /** @@ -95,7 +121,15 @@ public class FileInput { * @return */ public static String read4ScannerZ(InputStream inputStream) { - return new Scanner(inputStream).useDelimiter("\\Z").next(); + Scanner s = new Scanner(inputStream).useDelimiter("\\Z"); + try { + return s.hasNext() ? s.next() : ""; + } catch (NoSuchElementException e) { + LOGGER.log(System.Logger.Level.WARNING, IO_EXCEPTION_MSG, e); + throw e; + } finally { + s.close(); + } } /** @@ -108,9 +142,15 @@ public class FileInput { public static String read4StringBuilder(InputStream inputStream, Charset charset) throws IOException { StringBuilder sb = new StringBuilder(); String line; - BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, charset == null ? Charset.defaultCharset() : charset)); - while ((line = br.readLine()) != null) { - sb.append(line); + String lineSeparator = System.lineSeparator(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, charset == null ? Charset.defaultCharset() : charset))) { + while ((line = br.readLine()) != null) { + sb.append(line).append(lineSeparator); + } + // 删除末尾多余的换行符 + if (sb.length() > 0) { + sb.delete(sb.length() - lineSeparator.length(), sb.length()); + } } return sb.toString(); } @@ -184,17 +224,21 @@ public class FileInput { * @return * @throws IOException */ - public static String read4CharStreams(InputStream inputStream, Charset charset) throws IOException, ClassNotFoundException { + public static String read4CharStreams(InputStream inputStream, + Charset charset) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, + IllegalAccessException { try { Class charStreamsClass = Class.forName("com.google.common.io.CharStreams"); - Method toStringMethod = charStreamsClass.getMethod("toString", InputStreamReader.class); + String method = "toString"; + Method toStringMethod = charStreamsClass.getMethod(method, Readable.class); return (String) toStringMethod.invoke(null, new InputStreamReader(inputStream, charset == null ? Charset.defaultCharset() : charset)); } catch (ClassNotFoundException e) { - throw new ClassNotFoundException("缺少依赖,请引入Guava"); + LOGGER.log(System.Logger.Level.DEBUG, "缺少依赖,请引入Guava"); + throw e; } catch (ReflectiveOperationException e) { - throw new RuntimeException("com.google.common.io.CharStreams.toString调用失败,请检查Guava版本", e); + LOGGER.log(System.Logger.Level.DEBUG, "com.google.common.io.CharStreams.toString调用失败,请检查Guava版本"); + throw e; } -// return com.google.common.io.CharStreams.toString(new InputStreamReader(inputStream, charset == null ? Charset.defaultCharset() : charset)); } /** @@ -205,50 +249,20 @@ public class FileInput { * @return * @throws IOException */ - public static String read4ByteStreams(InputStream inputStream, Charset charset) throws IOException, ClassNotFoundException { + public static String read4ByteStreams(InputStream inputStream, + Charset charset) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, + IllegalAccessException { try { Class charStreamsClass = Class.forName("com.google.common.io.ByteStreams"); - Method toStringMethod = charStreamsClass.getMethod("toByteArray", InputStreamReader.class); + String method = "toByteArray"; + Method toStringMethod = charStreamsClass.getMethod(method, InputStreamReader.class); return (String) toStringMethod.invoke(null, new InputStreamReader(inputStream, charset == null ? Charset.defaultCharset() : charset)); } catch (ClassNotFoundException e) { - throw new ClassNotFoundException("缺少依赖,请引入Guava"); + LOGGER.log(System.Logger.Level.DEBUG, "缺少依赖,请引入Guava"); + throw e; } catch (ReflectiveOperationException e) { - throw new RuntimeException("com.google.common.io.ByteStreams.toByteArray调用失败,请检查Guava版本", e); + LOGGER.log(System.Logger.Level.DEBUG, "com.google.common.io.ByteStreams.toByteArray调用失败,请检查Guava版本"); + throw e; } -// return new String(com.google.common.io.ByteStreams.toByteArray(inputStream), charset == null ? Charset.defaultCharset() : charset); } } - - - /*public static void main(String[] args) { - long size = FileUtil.size(new File("E:\\OS\\deepin-15.6-amd64\\DeepinCloudPrintServerInstaller_1.0.0.1.exe")); - System.out.println(size); - System.out.println(1024 * 1024 * 5); - if (size > 1024 * 1024 * 5) { - System.out.println("文件最大5M"); - return; - } - - long s1 = fileSize(new File("E:\\OS\\cn_windows_10_consumer_editions_version_1803_updated_march_2018_x64_dvd_12063766.iso")); - System.out.println(s1); - long s2 = fileSize4Stream(new File("E:\\OS\\cn_windows_10_consumer_editions_version_1803_updated_march_2018_x64_dvd_12063766.iso")); - System.out.println(s2); - - String s1 = base64(new File("C:\\Users\\Administrator\\Desktop\\a.html")); - System.out.println(s1); - - String s = sha1(new File("C:\\Users\\Administrator\\Desktop\\a.html")); - String s2 = sha1ByBigFile(new File("C:\\Users\\Administrator\\Desktop\\a.html")); - System.out.println(s); - System.out.println(s2); - - - String md5 = md5(new File("C:\\Users\\Administrator\\Desktop\\a.html")); - String md52 = md5ByBigFile(new File("C:\\Users\\Administrator\\Desktop\\a.html")); - System.out.println(md5); - System.out.println(md52); - - - String crc32 = crc32(new File("C:\\Users\\Administrator\\Desktop\\a.html")); - System.out.println(crc32); - }*/ diff --git a/src/main/java/com/yexuejc/base/util/DateTimeUtil.java b/src/main/java/com/yexuejc/base/util/DateTimeUtil.java index ec644fd..d150887 100644 --- a/src/main/java/com/yexuejc/base/util/DateTimeUtil.java +++ b/src/main/java/com/yexuejc/base/util/DateTimeUtil.java @@ -5,10 +5,10 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.temporal.Temporal; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalAdjusters; import java.util.Date; @@ -168,9 +168,7 @@ public class DateTimeUtil { */ public static Date parseDate(LocalDate localDate) { ZoneId zone = ZoneId.systemDefault(); - Instant instant = localDate.atStartOfDay() - .atZone(zone) - .toInstant(); + Instant instant = localDate.atStartOfDay().atZone(zone).toInstant(); return Date.from(instant); } @@ -207,8 +205,7 @@ public class DateTimeUtil { public static Date parseDate(LocalDate localDate, LocalTime localTime) { LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); ZoneId zone = ZoneId.systemDefault(); - Instant instant = localDateTime.atZone(zone) - .toInstant(); + Instant instant = localDateTime.atZone(zone).toInstant(); return Date.from(instant); } @@ -221,8 +218,7 @@ public class DateTimeUtil { public static ZonedDateTime parseZonedDateTime(Date date) { Instant instant = date.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); - return instant.atZone(zoneId) - .withZoneSameInstant(zoneId); + return instant.atZone(zoneId).withZoneSameInstant(zoneId); } /** @@ -234,8 +230,7 @@ public class DateTimeUtil { public static LocalDateTime parseLocalDateTime(Date date) { Instant instant = date.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); - return instant.atZone(zoneId) - .toLocalDateTime(); + return instant.atZone(zoneId).toLocalDateTime(); } /** @@ -247,8 +242,7 @@ public class DateTimeUtil { public static LocalDate parseLocalDate(Date date) { Instant instant = date.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); - return instant.atZone(zoneId) - .toLocalDate(); + return instant.atZone(zoneId).toLocalDate(); } /** @@ -315,8 +309,7 @@ public class DateTimeUtil { * @return */ public static LocalDateTime parserUTC(Long timestamp) { - if (String.valueOf(timestamp) - .length() == 10) { + if (String.valueOf(timestamp).length() == 10) { timestamp = timestamp * 1000; } return parseLocalDateTime13(timestamp, ZoneId.of("UTC")); @@ -330,8 +323,7 @@ public class DateTimeUtil { */ public static long parseLong(LocalDateTime localDateTime) { ZoneId zone = ZoneId.systemDefault(); - Instant instant = localDateTime.atZone(zone) - .toInstant(); + Instant instant = localDateTime.atZone(zone).toInstant(); return instant.toEpochMilli(); } @@ -343,49 +335,10 @@ public class DateTimeUtil { */ public static long parseLong(LocalDate localDate) { ZoneId zone = ZoneId.systemDefault(); - Instant instant = localDate.atStartOfDay(zone) - .toInstant(); + Instant instant = localDate.atStartOfDay(zone).toInstant(); return instant.toEpochMilli(); } - - /** - * 格式化时间
- * 格式 yyyy-MM-dd HH:mm:ss - * - * @param dateTime - * @return - */ - public static String format(LocalDate dateTime) { - return format(dateTime, null); - } - - /** - * 格式化时间 - * - * @param dateTime - * @param pattern 格式 默认:yyyy-MM-dd - * @return - */ - public static String format(LocalDate dateTime, String pattern) { - if (StrUtil.isEmpty(pattern)) { - pattern = DateConsts.DATE_PATTERN; - } - DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern); - return df.format(dateTime); - } - - /** - * 格式化时间
- * 格式 yyyy-MM-dd HH:mm:ss - * - * @param dateTime - * @return - */ - public static String format(LocalDateTime dateTime) { - return format(dateTime, null); - } - /** * 格式化时间 * @@ -393,13 +346,20 @@ public class DateTimeUtil { * @param pattern 格式 默认:yyyy-MM-dd HH:mm:ss * @return */ - public static String format(LocalDateTime dateTime, String pattern) { + public static String format(Temporal dateTime, String pattern) { if (StrUtil.isEmpty(pattern)) { - pattern = DateConsts.DATE_TIME_PATTERN; + if (dateTime instanceof LocalDate) { + // yyyy-MM-dd + pattern = DateConsts.DATE_PATTERN; + } else { + // yyyy-MM-dd HH:mm:ss + pattern = DateConsts.DATE_TIME_PATTERN; + } } DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern); return df.format(dateTime); } + /** * 格式化时间
* 格式 yyyy-MM-dd HH:mm:ss @@ -407,25 +367,10 @@ public class DateTimeUtil { * @param dateTime * @return */ - public static String format(OffsetDateTime dateTime) { + public static String format(Temporal dateTime) { return format(dateTime, null); } - /** - * 格式化时间 - * - * @param dateTime - * @param pattern 格式 默认:yyyy-MM-dd HH:mm:ss - * @return - */ - public static String format(OffsetDateTime dateTime, String pattern) { - if (StrUtil.isEmpty(pattern)) { - pattern = DateConsts.DATE_TIME_PATTERN; - } - DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern); - return df.format(dateTime); - } - /** * 获取UTC(格林威治,标准)时间 * @@ -464,9 +409,7 @@ public class DateTimeUtil { currentZone = ZoneId.of(currentZoneId); } ZoneId targetZone = ZoneId.of(targetZoneId); - return date.atZone(currentZone) - .withZoneSameInstant(targetZone) - .toLocalDateTime(); + return date.atZone(currentZone).withZoneSameInstant(targetZone).toLocalDateTime(); } /** @@ -475,7 +418,7 @@ public class DateTimeUtil { * @return 2019-05-28T12:12:12+08:00 */ public static String formatIso8601BySystemZone() { - return DateTimeUtil.format(OffsetDateTime.now(), DateConsts.ISO_8601_PATTERN); + return DateTimeUtil.format(ZonedDateTime.now(), DateConsts.ISO_8601_PATTERN); } /** @@ -485,7 +428,8 @@ public class DateTimeUtil { * @return 2019-05-28T12:12:12+08:00 */ public static String formatIso8601BySystemZone(LocalDateTime localDateTime) { - return DateTimeUtil.format(localDateTime, DateConsts.ISO_8601_PATTERN); + ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault()); + return DateTimeUtil.format(zonedDateTime, DateConsts.ISO_8601_PATTERN); } /** @@ -499,8 +443,15 @@ public class DateTimeUtil { return DateTimeUtil.format(parseLocalDateTime13(timestamp, zoneId), DateConsts.ISO_8601_PATTERN); } - /*public static void main(String[] args) throws ParseException { + System.out.println(format(LocalDate.now(), DateConsts.DATE_PATTERN)); + System.out.println(format(LocalDateTime.now(), DateConsts.DATE_TIME_MS_PATTERN)); + System.out.println(format(OffsetDateTime.now(), DateConsts.ISO_8601_MS_PATTERN)); + System.out.println(format(ZonedDateTime.now(), DateConsts.ISO_8601_MS_PATTERN)); + System.out.println(format(LocalDate.now())); + System.out.println(format(LocalDateTime.now())); + System.out.println(format(OffsetDateTime.now())); + System.out.println(format(ZonedDateTime.now())); System.out.println(parserUTC(1684140338161L)); System.out.println(convertUTC(LocalDateTime.now())); Date date = DateUtil.str2dateTime("2023-05-15 08:28:05.327"); diff --git a/src/test/java/com/yexuejc/base/encrypt/AESTest.java b/src/test/java/com/yexuejc/base/encrypt/AESTest.java index b56eefd..162f10e 100644 --- a/src/test/java/com/yexuejc/base/encrypt/AESTest.java +++ b/src/test/java/com/yexuejc/base/encrypt/AESTest.java @@ -1,55 +1,207 @@ package com.yexuejc.base.encrypt; +import com.yexuejc.base.exception.BaseException; +import org.junit.jupiter.api.Test; + import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; +/** + * AES加密解密工具类测试 + */ +class AESTest { - -public class AESTest { + private static final String TEST_KEY = "1234567890123456"; // 16位密钥 + private static final String TEST_IV = "abcdefghijklmnop"; // 16位初始向量 @Test - public void testEncrypt() throws Exception { - String data = "Hello World!"; + void testBuilder() { + AES aes = AES.builder(); + assertNotNull(aes); + } + + @Test + void testEncryptAndDecrypt() throws BaseException { AES aes = AES.builder() + .setKey(TEST_KEY) + .setIv(TEST_IV); + + String plainText = "Hello, World!"; + String encrypted = aes.encrypt(plainText); + String decrypted = aes.decrypt(encrypted); + + assertNotEquals(plainText, encrypted); + assertEquals(plainText, decrypted); + } + + @Test + void testDifferentAlgorithms() throws BaseException { + String plainText = "This is a test message for different algorithms."; + + // 测试CBC模式 + AES aesCbc = AES.builder() + .setKey(TEST_KEY) + .setIv(TEST_IV) + .setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding); + + String encryptedCbc = aesCbc.encrypt(plainText); + String decryptedCbc = aesCbc.decrypt(encryptedCbc); + assertEquals(plainText, decryptedCbc); + + // 测试ECB模式 (不需要IV) + AES aesEcb = AES.builder() + .setKey(TEST_KEY) + .setAlgorithm(AES.ALGORITHM.AES_ECB_PKCS5Padding); + + String encryptedEcb = aesEcb.encrypt(plainText); + String decryptedEcb = aesEcb.decrypt(encryptedEcb); + assertEquals(plainText, decryptedEcb); + } + + @Test + void testSetKeyWithInvalidLength() { + AES aes = AES.builder(); + + // 测试空密钥 + IllegalArgumentException thrown1 = assertThrows(IllegalArgumentException.class, () -> aes.setKey(null)); + assertEquals("AES密钥不能为空", thrown1.getMessage()); + + // 测试长度不足的密钥 + IllegalArgumentException thrown2 = assertThrows(IllegalArgumentException.class, () -> aes.setKey("12345")); + assertEquals("AES密钥长度必须是16、24或32字节", thrown2.getMessage()); + + // 测试长度正确的密钥 + assertDoesNotThrow(() -> aes.setKey(TEST_KEY)); + } + + @Test + void testSetIvWithInvalidLength() { + AES aes = AES.builder(); + + // 测试空IV + IllegalArgumentException thrown1 = assertThrows(IllegalArgumentException.class, () -> aes.setIv(null)); + assertEquals("AES初始向量不能为空", thrown1.getMessage()); + + // 测试长度不足的IV + IllegalArgumentException thrown2 = assertThrows(IllegalArgumentException.class, () -> aes.setIv("12345")); + assertEquals("AES初始向量长度必须是16字节", thrown2.getMessage()); + + // 测试长度正确的IV + assertDoesNotThrow(() -> aes.setIv(TEST_IV)); + } + + @Test + void testEncryptWithoutSettingKey() { + AES aes = AES.builder(); + + IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> aes.encrypt("test")); + assertEquals("AES密钥未设置,请调用setKey()方法设置密钥", thrown.getMessage()); + } + + @Test + void testEncryptWithoutSettingIv() { + AES aes = AES.builder() + .setKey(TEST_KEY); + + IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> aes.encrypt("test")); + assertEquals("AES初始向量未设置,请调用setIv()方法设置IV", thrown.getMessage()); + } + + @Test + void testEncryptWithEmptyData() { + AES aes = AES.builder() + .setKey(TEST_KEY) + .setIv(TEST_IV); + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> aes.encrypt("")); + assertEquals("加密数据不能为空", thrown.getMessage()); + } + + @Test + void testDecryptWithEmptyData() { + AES aes = AES.builder() + .setKey(TEST_KEY) + .setIv(TEST_IV); + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> aes.decrypt("")); + assertEquals("解密数据不能为空", thrown.getMessage()); + } + + @Test + void testSetCharset() { + AES aes = AES.builder(); + + // 测试正常设置字符集 + assertDoesNotThrow(() -> aes.setCharset(StandardCharsets.UTF_8)); + + // 测试设置null字符集 + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> aes.setCharset(null)); + assertEquals("字符集不能为空", thrown.getMessage()); + } + + @Test + void testGettersAndSetters() { + AES aes = AES.builder(); + + // 测试算法设置和获取 + aes.setAlgorithm(AES.ALGORITHM.AES_CBC_NoPadding); + assertEquals(AES.ALGORITHM.AES_CBC_NoPadding, aes.getAlgorithm()); + + // 测试密钥设置和获取 + aes.setKey(TEST_KEY); + assertEquals(TEST_KEY, aes.getKey()); + + // 测试IV设置和获取 + aes.setIv(TEST_IV); + assertEquals(TEST_IV, aes.getIv()); + + // 测试字符集获取 + assertEquals(StandardCharsets.UTF_8, aes.getCharset()); + } + + @Test + void testChainCalls() { + AES aes = AES.builder() + .setKey(TEST_KEY) + .setIv(TEST_IV) .setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding) - .setKey("hj7x89H$yuBI0456") - .setIv("NIfb&95GUY86Gfgh") .setCharset(StandardCharsets.UTF_8); - String encrypted = aes.encrypt(data); - assertNotNull(encrypted); - assertFalse(encrypted.isEmpty()); + + assertNotNull(aes.getKey()); + assertNotNull(aes.getIv()); + assertNotNull(aes.getAlgorithm()); + assertNotNull(aes.getCharset()); } @Test - public void testDecrypt() throws Exception { - String data = "p0x0vK5T6OOy69+p9cgI/9xfeoi/f0t6NO7HbLsUON4="; + void testSpecialCharacters() throws BaseException { AES aes = AES.builder() - .setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding) - .setKey("hj7x89H$yuBI0456") - .setIv("NIfb&95GUY86Gfgh") - .setCharset(StandardCharsets.UTF_8); - String decrypted = aes.decrypt(data); - assertNotNull(decrypted); - assertFalse(decrypted.isEmpty()); - assertEquals("Hello World!", decrypted); + .setKey(TEST_KEY) + .setIv(TEST_IV); + + String plainText = "特殊字符测试: !@#$%^&*()_+-={}[]|\\:\";'<>?,./ 中文测试"; + String encrypted = aes.encrypt(plainText); + String decrypted = aes.decrypt(encrypted); + + assertEquals(plainText, decrypted); } @Test - public void testEncryptAndDecrypt() throws Exception { - String data = "张三"; + void testLongText() throws BaseException { AES aes = AES.builder() - .setAlgorithm(AES.ALGORITHM.AES_OFB_ISO10126Padding) - .setKey("hj7x89H$yuBI0456") - .setIv("NIfb&95GUY86Gfgh") - .setCharset(StandardCharsets.UTF_8); - String encrypt = aes.encrypt(data); - System.out.println("加密:" + encrypt); - String decrypt = aes.decrypt(encrypt); - System.out.println("解密:" + decrypt); - } + .setKey(TEST_KEY) + .setIv(TEST_IV); -} + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + sb.append("这是一个用于测试长文本加密解密的文本。"); + } + String plainText = sb.toString(); + + String encrypted = aes.encrypt(plainText); + String decrypted = aes.decrypt(encrypted); + + assertEquals(plainText, decrypted); + } +} \ No newline at end of file diff --git a/src/test/java/com/yexuejc/base/encrypt/DES3NewTest.java b/src/test/java/com/yexuejc/base/encrypt/DES3NewTest.java new file mode 100644 index 0000000..c0e1a8a --- /dev/null +++ b/src/test/java/com/yexuejc/base/encrypt/DES3NewTest.java @@ -0,0 +1,187 @@ +package com.yexuejc.base.encrypt; + +import com.yexuejc.base.exception.BaseException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * DES3加密解密工具类测试(新版本) + */ +class DES3NewTest { + + private static final String VALID_KEY = "123456789012345678901234"; // 24位有效密钥 + private static final String INVALID_KEY = "123456"; // 无效密钥,长度不足 + + @Test + void testEncryptDesCbcWithValidKey() throws BaseException { + String plainText = "Hello, World!"; + String encrypted = DES3.encryptDesCbc(plainText, VALID_KEY); + String decrypted = DES3.decryptDesCbc(encrypted, VALID_KEY); + + assertNotNull(encrypted); + assertNotEquals(plainText, encrypted); // 确保加密后的内容与原文不同 + assertEquals(plainText, decrypted); + } + + @Test + void testEncryptAndDecryptChineseCharacters() throws BaseException { + String plainText = "你好,世界!"; + String encrypted = DES3.encryptDesCbc(plainText, VALID_KEY); + String decrypted = DES3.decryptDesCbc(encrypted, VALID_KEY); + + assertNotNull(encrypted); + assertNotEquals(plainText, encrypted); + assertEquals(plainText, decrypted); + } + + @Test + void testEncryptAndDecryptSpecialCharacters() throws BaseException { + String plainText = "!@#$%^&*()_+-=[]{}|;':\",./<>?"; + String encrypted = DES3.encryptDesCbc(plainText, VALID_KEY); + String decrypted = DES3.decryptDesCbc(encrypted, VALID_KEY); + + assertNotNull(encrypted); + assertNotEquals(plainText, encrypted); + assertEquals(plainText, decrypted); + } + + @Test + void testEncryptDesCbcWithNullKey() { + String plainText = "Hello, World!"; + + BaseException exception = assertThrows(BaseException.class, () -> { + DES3.encryptDesCbc(plainText, null); + }); + + assertEquals("INVALID_KEY_LENGTH", exception.getErrorCode()); + } + + @Test + void testEncryptDesCbcWithInvalidKey() { + String plainText = "Hello, World!"; + + BaseException exception = assertThrows(BaseException.class, () -> { + DES3.encryptDesCbc(plainText, INVALID_KEY); + }); + + assertEquals("INVALID_KEY_LENGTH", exception.getErrorCode()); + } + + @Test + void testDecryptDesCbcWithNullKey() { + String encryptedText = "someEncryptedText"; + + BaseException exception = assertThrows(BaseException.class, () -> { + DES3.decryptDesCbc(encryptedText, null); + }); + + assertEquals("INVALID_KEY_LENGTH", exception.getErrorCode()); + } + + @Test + void testDecryptDesCbcWithInvalidKey() { + String encryptedText = "someEncryptedText"; + + BaseException exception = assertThrows(BaseException.class, () -> { + DES3.decryptDesCbc(encryptedText, INVALID_KEY); + }); + + assertEquals("INVALID_KEY_LENGTH", exception.getErrorCode()); + } + + @Test + void testDecryptWithInvalidEncryptedData() { + String invalidEncryptedText = "invalidBase64String!"; + + BaseException exception = assertThrows(BaseException.class, () -> { + DES3.decryptDesCbc(invalidEncryptedText, VALID_KEY); + }); + + assertEquals("DECRYPTION_PARAM_FAILED", exception.getErrorCode()); + } + + @Test + void testEncryptAndDecryptEmptyString() throws BaseException { + String plainText = ""; + String encrypted = DES3.encryptDesCbc(plainText, VALID_KEY); + String decrypted = DES3.decryptDesCbc(encrypted, VALID_KEY); + + assertNotNull(encrypted); + assertNotEquals(plainText, encrypted); + assertEquals(plainText, decrypted); + } + + @Test + void testEncryptAndDecryptLongText() throws BaseException { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + sb.append("这是用于测试长文本加密解密的文本。"); + } + String plainText = sb.toString(); + + String encrypted = DES3.encryptDesCbc(plainText, VALID_KEY); + String decrypted = DES3.decryptDesCbc(encrypted, VALID_KEY); + + assertNotNull(encrypted); + assertNotEquals(plainText, encrypted); + assertEquals(plainText, decrypted); + } + + @Test + void testPaddingFunction() { + // 测试长度为7的字符串(需要填充1个字节) + String input1 = "1234567"; + String padded1 = DES3.padding(input1); + assertEquals(8, padded1.length()); + assertTrue(padded1.startsWith("1234567")); + + // 测试长度为8的字符串(不需要填充) + String input2 = "12345678"; + String padded2 = DES3.padding(input2); + assertEquals(8, padded2.length()); // 长度已经是8的倍数,不需要额外填充 + assertTrue(padded2.startsWith("12345678")); + + // 测试长度为9的字符串(需要填充7个字节) + String input3 = "123456789"; + String padded3 = DES3.padding(input3); + assertEquals(16, padded3.length()); + assertTrue(padded3.startsWith("123456789")); + + // 测试空字符串(需要填充8个字节) + String input4 = ""; + String padded4 = DES3.padding(input4); + assertEquals(8, padded4.length()); + } + + @Test + void testDifferentEncryptionsProduceDifferentResults() throws BaseException { + String plainText = "consistent test message"; + String encrypted1 = DES3.encryptDesCbc(plainText, VALID_KEY); + String encrypted2 = DES3.encryptDesCbc(plainText, VALID_KEY); + + // 同样的明文和密钥应该产生不同的密文(因为使用随机IV) + assertNotEquals(encrypted1, encrypted2); + + // 但是解密后应该能得到相同原文 + String decrypted1 = DES3.decryptDesCbc(encrypted1, VALID_KEY); + String decrypted2 = DES3.decryptDesCbc(encrypted2, VALID_KEY); + assertEquals(plainText, decrypted1); + assertEquals(plainText, decrypted2); + } + + @Test + void testMinimumKeyLengthRequirement() { + // 测试23位密钥(不够24位) + String key23 = "12345678901234567890123"; + BaseException exception23 = assertThrows(BaseException.class, () -> { + DES3.encryptDesCbc("test", key23); + }); + assertEquals("INVALID_KEY_LENGTH", exception23.getErrorCode()); + + // 测试正好24位密钥(应该正常工作) + assertDoesNotThrow(() -> { + DES3.encryptDesCbc("test", VALID_KEY); + }); + } +} \ No newline at end of file diff --git a/src/test/java/com/yexuejc/base/encrypt/DES3Test.java b/src/test/java/com/yexuejc/base/encrypt/DES3Test.java new file mode 100644 index 0000000..f16f762 --- /dev/null +++ b/src/test/java/com/yexuejc/base/encrypt/DES3Test.java @@ -0,0 +1,141 @@ +package com.yexuejc.base.encrypt; + +import com.yexuejc.base.exception.BaseException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * DES3加密解密工具类测试 + */ +class DES3Test { + + private static final String VALID_KEY = "123456789012345678901234"; // 24位有效密钥 + private static final String INVALID_KEY = "123456"; // 无效密钥,长度不足 + + @Test + void testEncryptDesCbcWithValidKey() throws BaseException { + String plainText = "Hello, World!"; + String encrypted = DES3.encryptDesCbc(plainText, VALID_KEY); + String decrypted = DES3.decryptDesCbc(encrypted, VALID_KEY); + + assertNotNull(encrypted); + assertEquals(plainText, decrypted); + } + + @Test + void testEncryptAndDecryptChineseCharacters() throws BaseException { + String plainText = "你好,世界!"; + String encrypted = DES3.encryptDesCbc(plainText, VALID_KEY); + String decrypted = DES3.decryptDesCbc(encrypted, VALID_KEY); + + assertNotNull(encrypted); + assertEquals(plainText, decrypted); + } + + @Test + void testEncryptAndDecryptSpecialCharacters() throws BaseException { + String plainText = "!@#$%^&*()_+-=[]{}|;':\",./<>?"; + String encrypted = DES3.encryptDesCbc(plainText, VALID_KEY); + String decrypted = DES3.decryptDesCbc(encrypted, VALID_KEY); + + assertNotNull(encrypted); + assertEquals(plainText, decrypted); + } + + @Test + void testEncryptDesCbcWithInvalidKey() { + String plainText = "Hello, World!"; + + BaseException exception = assertThrows(BaseException.class, () -> { + DES3.encryptDesCbc(plainText, INVALID_KEY); + }); + + assertEquals("INVALID_KEY_LENGTH:key的length不得小于24。", exception.getMessage()); + } + + @Test + void testDecryptDesCbcWithInvalidKey() { + String encryptedText = "someEncryptedText"; + + BaseException exception = assertThrows(BaseException.class, () -> { + DES3.decryptDesCbc(encryptedText, INVALID_KEY); + }); + + assertEquals("INVALID_KEY_LENGTH:key的length不得小于24。", exception.getMessage()); + } + + @Test + void testDecryptWithInvalidEncryptedData() { + String invalidEncryptedText = "invalidBase64String!"; + + BaseException exception = assertThrows(BaseException.class, () -> { + DES3.decryptDesCbc(invalidEncryptedText, VALID_KEY); + }); + } + + @Test + void testEncryptAndDecryptEmptyString() throws BaseException { + String plainText = ""; + String encrypted = DES3.encryptDesCbc(plainText, VALID_KEY); + String decrypted = DES3.decryptDesCbc(encrypted, VALID_KEY); + + assertNotNull(encrypted); + assertEquals(plainText, decrypted); + } + + @Test + void testEncryptAndDecryptLongText() throws BaseException { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + sb.append("这是用于测试长文本加密解密的文本。"); + } + String plainText = sb.toString(); + + String encrypted = DES3.encryptDesCbc(plainText, VALID_KEY); + String decrypted = DES3.decryptDesCbc(encrypted, VALID_KEY); + + assertNotNull(encrypted); + assertEquals(plainText, decrypted); + } + + @Test + void testPaddingFunction() { + // 测试长度为7的字符串(需要填充1个字节) + String input1 = "1234567"; + String padded1 = DES3.padding(input1); + assertEquals(8, padded1.length()); + assertTrue(padded1.startsWith("1234567")); + + // 测试长度为8的字符串(不需要填充) + String input2 = "12345678"; + String padded2 = DES3.padding(input2); + assertEquals(8, padded2.length()); + assertTrue(padded2.startsWith("12345678")); + + // 测试长度为9的字符串(需要填充7个字节) + String input3 = "123456789"; + String padded3 = DES3.padding(input3); + assertEquals(16, padded3.length()); + assertTrue(padded3.startsWith("123456789")); + + // 测试空字符串(需要填充8个字节) + String input4 = ""; + String padded4 = DES3.padding(input4); + assertEquals(0, padded4.length()); + } + + @Test + void testConsistentEncryption() throws BaseException { + String plainText = "consistent test message"; + String encrypted1 = DES3.encryptDesCbc(plainText, VALID_KEY); + String encrypted2 = DES3.encryptDesCbc(plainText, VALID_KEY); + assertNotEquals(encrypted1, encrypted2); + + // 解密应该能得到原文 + String decrypted = DES3.decryptDesCbc(encrypted1, VALID_KEY); + String decrypted2 = DES3.decryptDesCbc(encrypted2, VALID_KEY); + assertEquals(plainText, decrypted); + assertEquals(plainText, decrypted2); + } +} \ No newline at end of file diff --git a/src/test/java/com/yexuejc/base/encrypt/PaddingDebugTest.java b/src/test/java/com/yexuejc/base/encrypt/PaddingDebugTest.java new file mode 100644 index 0000000..7797ee4 --- /dev/null +++ b/src/test/java/com/yexuejc/base/encrypt/PaddingDebugTest.java @@ -0,0 +1,23 @@ +package com.yexuejc.base.encrypt; + +public class PaddingDebugTest { + public static void main(String[] args) { + // 测试padding函数的行为 + System.out.println("Testing padding function:"); + + String input1 = "12345678"; + String padded1 = DES3.padding(input1); + System.out.println("Input: '" + input1 + "' Length: " + input1.length()); + System.out.println("Output: '" + padded1 + "' Length: " + padded1.length()); + + String input2 = "1234567"; + String padded2 = DES3.padding(input2); + System.out.println("Input: '" + input2 + "' Length: " + input2.length()); + System.out.println("Output: '" + padded2 + "' Length: " + padded2.length()); + + String input3 = ""; + String padded3 = DES3.padding(input3); + System.out.println("Input: '" + input3 + "' Length: " + input3.length()); + System.out.println("Output: '" + padded3 + "' Length: " + padded3.length()); + } +} \ No newline at end of file diff --git a/src/test/java/com/yexuejc/base/encrypt/RSA2Test.java b/src/test/java/com/yexuejc/base/encrypt/RSA2Test.java new file mode 100644 index 0000000..2fc0221 --- /dev/null +++ b/src/test/java/com/yexuejc/base/encrypt/RSA2Test.java @@ -0,0 +1,179 @@ +package com.yexuejc.base.encrypt; + +import com.yexuejc.base.exception.BaseException; +import org.junit.jupiter.api.Test; + +import java.io.*; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.cert.CertificateException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * RSA2类的单元测试 + */ +class RSA2Test { + + /** + * 测试获取公钥的方法 - 正常情况 + */ + @Test + void shouldGenerateAndReadPublicKeySuccessfully() throws Exception { + // 生成测试用的密钥对 + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(1024); + KeyPair keyPair = generator.generateKeyPair(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + + // 注意:由于RSA2类依赖于实际的证书文件, + // 我们只能测试一些基本的逻辑,而不能完全测试所有方法 + assertNotNull(publicKey); + assertNotNull(privateKey); + assertNotNull(publicKey.getModulus()); + assertNotNull(privateKey.getModulus()); + } + + /** + * 测试获取私钥的方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenReadingNonExistentPrivateKey() { + assertThrows(BaseException.class, () -> { + RSA2.getPrivateKey("non-existent-file.pem", "alias", "password"); + }); + } + + /** + * 测试获取公钥的方法 - 异常情况 + */ + @Test + void shouldThrowCertificateExceptionWhenReadingNonExistentPublicKey() { + assertThrows(CertificateException.class, () -> { + RSA2.getPublicKey("non-existent-file.crt"); + }); + } + + /** + * 测试从PKCS12获取私钥的方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenReadingNonExistentPrivateKeyFromPKCS12() { + assertThrows(BaseException.class, () -> { + RSA2.getPrivateKeyFromPKCS12("non-existent-file.pfx", "alias", "password"); + }); + } + + /** + * 测试从PKCS8获取私钥的方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenReadingNonExistentPrivateKeyFromPKCS8() { + assertThrows(BaseException.class, () -> { + RSA2.getPrivateKeyFromPKCS8("non-existent-file.pem", "password"); + }); + } + + /** + * 测试从PKCS1获取私钥的方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenReadingNonExistentPrivateKeyFromPKCS1() { + assertThrows(BaseException.class, () -> { + RSA2.getPrivateKeyFromPKCS1("non-existent-file.pem"); + }); + } + + /** + * 测试获取JKS私钥的方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenReadingNonExistentPrivateKeyFromJKS() { + assertThrows(BaseException.class, () -> { + RSA2.getPrivateKey("non-existent-file.jks", "alias", "password", "JKS"); + }); + } + + /** + * 测试证书格式转换方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenConvertingNonExistentCertificate() { + assertThrows(BaseException.class, () -> { + RSA2.cover2Pfx("non-existent-file.jks", "output.pfx", "password", "password"); + }); + + assertThrows(BaseException.class, () -> { + RSA2.cover2keyStore("non-existent-file.pfx", "output.jks", "password", "password"); + }); + } + + /** + * 测试从输入流获取公钥的方法 - 异常情况 + */ + @Test + void shouldThrowCertificateExceptionWhenReadingPublicKeyFromInvalidInputStream() { + InputStream invalidInputStream = new ByteArrayInputStream("invalid certificate data".getBytes()); + assertThrows(CertificateException.class, () -> { + RSA2.getPublicKey(invalidInputStream); + }); + } + + /** + * 测试从PKCS12输入流获取公钥的方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenReadingPublicKeyFromInvalidPKCS12InputStream() { + InputStream invalidInputStream = new ByteArrayInputStream("invalid certificate data".getBytes()); + assertThrows(BaseException.class, () -> { + RSA2.getPublicKeyFromPKCS12(invalidInputStream, "alias", "password"); + }); + } + + /** + * 测试从输入流获取私钥的方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenReadingPrivateKeyFromInvalidInputStream() { + InputStream invalidInputStream = new ByteArrayInputStream("invalid private key data".getBytes()); + assertThrows(BaseException.class, () -> { + RSA2.getPrivateKey(invalidInputStream, "alias", "password", "JKS"); + }); + } + + /** + * 测试从PKCS12输入流获取私钥的方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenReadingPrivateKeyFromInvalidPKCS12InputStream() { + InputStream invalidInputStream = new ByteArrayInputStream("invalid private key data".getBytes()); + assertThrows(BaseException.class, () -> { + RSA2.getPrivateKeyFromPKCS12(invalidInputStream, "alias", "password"); + }); + } + + /** + * 测试从PKCS8输入流获取私钥的方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenReadingPrivateKeyFromInvalidPKCS8InputStream() { + InputStream invalidInputStream = new ByteArrayInputStream("invalid private key data".getBytes()); + assertThrows(BaseException.class, () -> { + RSA2.getPrivateKeyFromPKCS8(invalidInputStream, "password"); + }); + } + + /** + * 测试从PKCS1输入流获取私钥的方法 - 异常情况 + */ + @Test + void shouldThrowBaseExceptionWhenReadingPrivateKeyFromInvalidPKCS1InputStream() { + InputStream invalidInputStream = new ByteArrayInputStream("invalid private key data".getBytes()); + assertThrows(BaseException.class, () -> { + RSA2.getPrivateKeyFromPKCS1(invalidInputStream); + }); + } +} \ No newline at end of file diff --git a/src/test/java/com/yexuejc/base/encrypt/RSATestNew.java b/src/test/java/com/yexuejc/base/encrypt/RSATestNew.java new file mode 100644 index 0000000..e455030 --- /dev/null +++ b/src/test/java/com/yexuejc/base/encrypt/RSATestNew.java @@ -0,0 +1,180 @@ +package com.yexuejc.base.encrypt; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * RSA类的单元测试 + */ +class RSATestNew { + + private RSA rsa; + private RSAPublicKey publicKey; + private RSAPrivateKey privateKey; + + @BeforeEach + void setUp() throws Exception { + rsa = RSA.builder(); + + // 生成测试用的密钥对 + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(1024); + KeyPair keyPair = generator.generateKeyPair(); + publicKey = (RSAPublicKey) keyPair.getPublic(); + privateKey = (RSAPrivateKey) keyPair.getPrivate(); + } + + @Test + void shouldGenerateValidKeyMapWithDefaultSettings() { + Map keys = rsa.initKeys(1024); + + assertNotNull(keys); + assertTrue(keys.containsKey("publicKey")); + assertTrue(keys.containsKey("privateKey")); + + assertFalse(keys.get("publicKey").isEmpty()); + assertFalse(keys.get("privateKey").isEmpty()); + } + + @Test + void shouldGenerateValidKeyMapWithBase64UrlSafeOption() { + Map keys = rsa.initKeys(1024, true); + + assertNotNull(keys); + assertTrue(keys.containsKey("publicKey")); + assertTrue(keys.containsKey("privateKey")); + + assertFalse(keys.get("publicKey").isEmpty()); + assertFalse(keys.get("privateKey").isEmpty()); + + // Base64URL安全字符串不应该包含+和/ + assertFalse(keys.get("publicKey").contains("+") || keys.get("publicKey").contains("/")); + assertFalse(keys.get("privateKey").contains("+") || keys.get("privateKey").contains("/")); + } + + @Test + void shouldParsePublicKeyCorrectly() throws Exception { + // 使用RSA类生成密钥对进行测试 + Map keys = rsa.initKeys(1024); + String pubStr = keys.get("publicKey"); + + RSAPublicKey parsedPub = rsa.getPublicKey(pubStr); + + // 验证解析出来的公钥不为空 + assertNotNull(parsedPub); + assertNotNull(parsedPub.getModulus()); + } + + @Test + void shouldParsePrivateKeyCorrectly() throws Exception { + // 使用RSA类生成密钥对进行测试 + Map keys = rsa.initKeys(1024); + String priStr = keys.get("privateKey"); + + RSAPrivateKey parsedPri = rsa.getPrivateKey(priStr); + + // 验证解析出来的私钥不为空 + assertNotNull(parsedPri); + assertNotNull(parsedPri.getModulus()); + } + + @Test + void shouldEncryptAndDecryptSuccessfullyUsingPublicPrivate() throws Exception { + String plainText = "Hello RSA!"; + String encrypted = rsa.publicEncrypt(plainText, publicKey); + String decrypted = rsa.privateDecrypt(encrypted, privateKey); + + assertEquals(plainText, decrypted); + } + + @Test + void shouldEncryptAndDecryptSuccessfullyUsingPrivatePublic() throws Exception { + String plainText = "Hello RSA Private->Public!"; + String encrypted = rsa.privateEncrypt(plainText, privateKey); + String decrypted = rsa.publicDecrypt(encrypted, publicKey); + + assertEquals(plainText, decrypted); + } + + @Test + void shouldSignAndVerifySuccessfully() throws Exception { + String message = "This is a signed message."; + String signature = rsa.sign(message, privateKey); + boolean verified = rsa.verify(message, signature, publicKey); + + assertTrue(verified); + } + + @Test + void shouldFailVerificationWhenMessageModified() throws Exception { + String originalMsg = "Original Message"; + String signature = rsa.sign(originalMsg, privateKey); + boolean result = rsa.verify("Modified Message", signature, publicKey); + + assertFalse(result); + } + + @Test + void shouldCreateKeyFilesWithoutException() throws Exception { + Path tempDir = Files.createTempDirectory("rsa-test"); + + assertDoesNotThrow(() -> rsa.initKey4File(tempDir.toString())); + + assertTrue(Files.exists(tempDir.resolve("private.key"))); + assertTrue(Files.exists(tempDir.resolve("public.key"))); + } + + // 注释掉需要Mockito的测试用例,因为项目中没有引入Mockito依赖 + /* + @Test + void shouldThrowBaseExceptionOnFileWriteFailure() throws IOException { + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + mockedFiles.when(() -> Files.writeString(any(Path.class), any(CharSequence.class))) + .thenThrow(new IOException("Simulated IO error")); + + assertThrows(BaseException.class, () -> rsa.initKey4File("/tmp")); + } + } + + @Test + void shouldHandleApiStyleSigningAndVerifying() throws Exception { + // 准备测试数据 + String uri = "/api/test"; + String body = "{\"id\":1}"; + + RequestHeader header = mock(RequestHeader.class); + when(header.getClientId()).thenReturn("client123"); + when(header.getReqTime()).thenReturn("20230101T120000Z"); + when(header.geRespTime()).thenReturn("20230101T120000Z"); + when(header.getSignature()).thenReturn("SIGNATURE=abc123"); + + // 使用Mock来模拟RSA2的行为 + try (MockedStatic mockedRSA2 = mockStatic(RSA2.class)) { + mockedRSA2.when(() -> RSA2.getPublicKey(anyString())).thenReturn(publicKey); + mockedRSA2.when(() -> RSA2.getPrivateKeyFromPKCS1(anyString())).thenReturn(privateKey); + + // 测试签名 + String signature = rsa.signByApi(uri, header, body, "/path/to/private.pem"); + + // 测试验证 + boolean verified = rsa.verifyByApi(uri, header, body, "/path/to/public.pem"); + + assertNotNull(signature); + assertTrue(verified); + } + } + */ +} \ No newline at end of file