2 Commits

Author SHA1 Message Date
maxf
c993cc6858 feat(util): 添加日期格式化方法
- 引入 DateConsts 常量类支持标准日期时间格式
- 新增 formatDate 方法简化日期对象格式化操作
- 默认使用 yyyy-MM-dd HH:mm:ss 格式输出日期时间
- 支持通过常量统一管理日期格式模式
- 提升日期工具类的易用性和代码复用性
2025-12-17 14:10:03 +08:00
maxf
7f8c46fa9b 🚧 进行中的工作/🔄 重构/ 测试: 优化AES、DES3和RSA加密实现并增强安全性
- 修改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
- 整体增强各加密类的安全性、健壮性和代码可维护性
2025-12-12 18:02:13 +08:00
19 changed files with 1455 additions and 451 deletions

View File

@@ -5,9 +5,9 @@ github:https://github.com/yexuejc/yexuejc-base
gitee:https://gitee.com/jzsw-it/yexuejc-base
### 说明
1. 支持环境java111.5.0开始支持java11请使用`1.5.x-jre11`版本)
1. 支持环境java211.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
<dependency>
<groupId>top.yexuejc</groupId>
<artifactId>yexuejc-base</artifactId>
<version>1.5.2-jre11</version>
<version>1.6.0-jre21</version>
</dependency>
</dependencies>
```

View File

@@ -1,5 +1,13 @@
yexuejc-base 更新记录
------------------
#### version 1.6.0-jre21
**time 2025-12-11 18:23:15** <br/>
**branch** jre21 <br/>
**update** <br/>
1. 全面升级到JDK21
---
#### version 1.5.7-jre11
**time 2025-12-11 18:10:47** <br/>
**branch** jre11 <br/>

View File

@@ -6,7 +6,7 @@
<groupId>top.yexuejc</groupId>
<artifactId>yexuejc-base</artifactId>
<version>1.5.7-jre11</version>
<version>1.6.0-jre21</version>
<name>yexuejc-base</name>
<url>https://github.com/yexuejc/yexuejc-base</url>
@@ -39,7 +39,7 @@
</scm>
<properties>
<java.version>11</java.version>
<java.version>21</java.version>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.verbose>true</maven.compiler.verbose>

View File

@@ -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";
}

View File

@@ -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 {
/**
* 构造函数 - 允许创建多个实例
* <p>
* 允许创建多个实例,避免单例模式的状态共享问题
*/
public AES() {
// 允许创建多个实例,避免单例模式的状态共享问题

View File

@@ -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);
}
}

View File

@@ -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 {
* <p>使用方式:</p>
* <p></p>
*/
public String algorithm = "RSA";
public volatile String algorithm = "RSA";
/**
* 加密方式
* <h5>示例:</h5>
@@ -95,12 +100,12 @@ public class RSA {
* <hr>
* <p><b>AES的算法/模式/填充)组合 参照 {@link ALGORITHM}<b/></p>
*/
public String transformation = "RSA";
public volatile String transformation = "RSA";
/**
* 是否每次改变加密结果
* 只针对于 transformation = "RSA"有效
*/
public boolean isChangeSign = true;
public volatile boolean isChangeSign = true;
/**
* 是否使用 Base64URL 方式加密 默认正常加密
* <pre>
@@ -121,7 +126,7 @@ public class RSA {
* };
* </pre>
*/
public boolean encodeBase64URLSafe = false;
public volatile boolean encodeBase64URLSafe = false;
/**
* 签名算法
*/
@@ -168,18 +173,23 @@ public class RSA {
*
* @param keySize 生成长度
* @param base64URLSafe 是否生成 base64URL 格式的密钥默认false
* @return
* @return 包含公钥和私钥的Mapkey为"publicKey"和"privateKey"
*/
public Map<String, String> initKeys(int keySize, boolean base64URLSafe) {
encodeBase64URLSafe = base64URLSafe;
boolean originalEncodeBase64URLSafe = this.encodeBase64URLSafe;
try {
this.encodeBase64URLSafe = base64URLSafe;
return initKeys(keySize);
} finally {
this.encodeBase64URLSafe = originalEncodeBase64URLSafe;
}
}
/**
* 生成密钥对
*
* @param keySize 生成长度
* @return
* @return 包含公钥和私钥的Mapkey为"publicKey"和"privateKey"
*/
public Map<String, String> initKeys(int keySize) {
//为RSA算法创建一个KeyPairGenerator对象
@@ -220,13 +230,13 @@ public class RSA {
* </p>
*
* @param filePath 密钥文件路径
* @throws BaseException
* @throws BaseException 生成密钥文件异常
*/
public void initKey4File(String filePath) throws BaseException {
Map<String, String> 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;
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;
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;
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,10 +633,10 @@ 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);
}
}

View File

@@ -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);

View File

@@ -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";
/**
* 解密<br>
* 用公钥解密
* 使用公钥解密数据
*
* @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);
}
/**
* 解密<br>
* 用私钥解密
* 使用私钥解密数据
*
* @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);
}
/**
* 加密<br>
* 用公钥加密
* 使用公钥加密数据
*
* @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);
}
/**
* 加密<br>
* 用私钥加密
* 使用私钥加密数据
*
* @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);
}
/**
* 解密<br>
* 用公钥解密
* 使用公钥解密数据
*
* @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);
}
/**
* 解密<br>
* 用私钥解密
* 使用私钥解密数据
*
* @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);
}
/**
* 加密<br>
* 用公钥加密
* 使用公钥加密数据
*
* @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);
}
/**
* 加密<br>
* 用私钥加密
* 使用私钥加密数据
*
* @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);
// 对数据解密

View File

@@ -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;

View File

@@ -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)}<br>
*/
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));
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);
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);
}*/

View File

@@ -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,33 +335,26 @@ 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();
}
/**
* 格式化时间 <br/>
* 格式 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
* @param pattern 格式 默认:yyyy-MM-dd HH:mm:ss
* @return
*/
public static String format(LocalDate dateTime, String pattern) {
public static String format(Temporal dateTime, String pattern) {
if (StrUtil.isEmpty(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);
@@ -382,50 +367,10 @@ public class DateTimeUtil {
* @param dateTime
* @return
*/
public static String format(LocalDateTime 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(LocalDateTime dateTime, String pattern) {
if (StrUtil.isEmpty(pattern)) {
pattern = DateConsts.DATE_TIME_PATTERN;
}
DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern);
return df.format(dateTime);
}
/**
* 格式化时间 <br/>
* 格式 yyyy-MM-dd HH:mm:ss
*
* @param dateTime
* @return
*/
public static String format(OffsetDateTime 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");

View File

@@ -8,6 +8,8 @@ import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import com.yexuejc.base.constant.DateConsts;
/**
* java.util.Date 时间工具类
*
@@ -166,6 +168,17 @@ public class DateUtil {
return sdf.parse(dateStr);
}
/**
* 格式化时间 <br/>
* 格式 yyyy-MM-dd HH:mm:ss
*
* @param dateTime
* @return
*/
public static String formatDate(Date dateTime) {
return formatDate(dateTime, DateConsts.DATE_TIME_PATTERN);
}
/**
* 根据指定的日期格式和地域格式化日期对象为字符串。
* 如果未指定地域,则默认使用中文地域格式。

View File

@@ -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);
}
}

View File

@@ -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);
});
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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);
});
}
}

View File

@@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<Files> 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<RSA2> 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);
}
}
*/
}