package com.shgbit.sfkc.thirdapi.judicial.utils;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class KeyUtil {
/**
* 加密算法RSA
*/
public static final String KEY_ALGORITHM = "RSA";
/**
* 签名算法
*/
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* <p>
* ⽤私钥对信息⽣成数字签名
* </p>
*
* @param data 已加密数据
* @param privateKey 私钥(BASE64编码)
* @return 签名
* @throws Exception 加密异常
*/
public static String sign(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateK);
signature.update(data);
return new String(Base64.getEncoder().encode(signature.sign()));
}
/**
* <p>
* 校验数字签名
* </p>
*
* @param data 已加密数据
* @param publicKey 公钥(BASE64编码)
* @param sign 数字签名
* @return 验证签名结果
* @throws Exception 验签异常
*/
public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicK);
signature.update(data);
return signature.verify(Base64.getDecoder().decode(sign));
}
/**
* <p>
* 用公钥加密
* </p>
*
* @param data 源数据
* @param publicKey 公钥(BASE64编码)
* @return 加密后的数据(BASE64编码)
* @throws Exception 加密异常
*/
public static String encryptByPublicKey(byte[] data, String publicKey) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicK);
int inputLen = data.length;
byte[] out = new byte[0];
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
byte[] temp = new byte[out.length + cache.length];
System.arraycopy(out, 0, temp, 0, out.length);
System.arraycopy(cache, 0, temp, out.length, cache.length);
out = temp;
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
return Base64.getEncoder().encodeToString(out);
}
/**
* <p>
* 用私钥解密
* </p>
*
* @param encryptedData 已加密数据(BASE64编码)
* @param privateKey 私钥(BASE64编码)
* @return 解密后的数据
* @throws Exception 解密异常
*/
public static byte[] decryptByPrivateKey(String encryptedData, String privateKey) throws Exception {
byte[] data = Base64.getDecoder().decode(encryptedData);
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateK);
int inputLen = data.length;
byte[] out = new byte[0];
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
byte[] temp = new byte[out.length + cache.length];
System.arraycopy(out, 0, temp, 0, out.length);
System.arraycopy(cache, 0, temp, out.length, cache.length);
out = temp;
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
return out;
}
/**
* <p>
* 用私钥加密
* </p>
*
* @param data 源数据
* @param privateKey 私钥(BASE64编码)
* @return 加密后的数据(BASE64编码)
* @throws Exception 加密异常
*/
public static String encryptByPrivateKey(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateK);
int inputLen = data.length;
byte[] out = new byte[0];
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
byte[] temp = new byte[out.length + cache.length];
System.arraycopy(out, 0, temp, 0, out.length);
System.arraycopy(cache, 0, temp, out.length, cache.length);
out = temp;
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
return Base64.getEncoder().encodeToString(out);
}
/**
* <p>
* 用公钥解密
* </p>
*
* @param encryptedData 已加密数据(BASE64编码)
* @param publicKey 公钥(BASE64编码)
* @return 解密后的数据
* @throws Exception 解密异常
*/
public static byte[] decryptByPublicKey(String encryptedData, String publicKey) throws Exception {
byte[] data = Base64.getDecoder().decode(encryptedData);
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicK);
int inputLen = data.length;
byte[] out = new byte[0];
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
byte[] temp = new byte[out.length + cache.length];
System.arraycopy(out, 0, temp, 0, out.length);
System.arraycopy(cache, 0, temp, out.length, cache.length);
out = temp;
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
return out;
}
}
- 代码概述
- 语言与依赖:Java,使用了 javax.crypto 和 java.security 包中的加密相关类(如 Cipher、KeyFactory、Signature),以及 java.util.Base64 用于处理 Base64 编码。
- 加密算法:使用 RSA(非对称加密算法),签名算法为 MD5withRSA。
- 主要功能:
- 加密/解密:支持公钥加密、私钥解密,以及私钥加密、公钥解密。
- 数字签名:使用私钥生成签名,使用公钥验证签名。
- 分段处理:由于 RSA 加密对数据长度有限制(明文最大 117 字节,密文最大 128 字节),代码实现了分段加密和解密。
- 应用场景:可能用于司法相关的第三方 API(如 sfkc.thirdapi.judicial 包名暗示),需要数据安全传输或身份验证的场景,例如与法院、司法机构的数据交互。
- 常量说明
以下是类中定义的常量及其含义:
- KEY_ALGORITHM = “RSA”:指定加密算法为 RSA,一种非对称加密算法,基于大整数分解的数学难题。
- SIGNATURE_ALGORITHM = “MD5withRSA”:签名算法,使用 MD5 哈希算法结合 RSA 加密生成数字签名。注:MD5 现已被认为不安全,实际生产环境中更推荐使用 SHA-256(如 SHA256withRSA)。
- MAX_ENCRYPT_BLOCK = 117:RSA 加密明文的最大块大小(单位:字节)。对于 1024 位 RSA 密钥,最大明文长度为密钥长度(128 字节)减去 11 字节的填充(PKCS#1 填充),即 128 - 11 = 117 字节。
- MAX_DECRYPT_BLOCK = 128:RSA 解密密文的最大块大小,等于密钥长度(1024 位密钥对应 128 字节)。
- 方法详细解析
以下是对每个方法的详细解释,包括功能、实现原理和代码逻辑。
3.1 sign(byte[] data, String privateKey)
- 功能:使用私钥对数据生成数字签名。
- 参数:
- data:待签名的数据(已加密或原始数据,字节数组)。
- privateKey:Base64 编码的私钥字符串。
- 返回值:Base64 编码的签名字符串。
- 实现步骤:
- 将 Base64 编码的私钥字符串解码为字节数组。
- 使用 PKCS8EncodedKeySpec 创建私钥规范(RSA 私钥的标准格式)。
- 通过 KeyFactory 生成私钥对象。
- 初始化 Signature 对象,使用 MD5withRSA 算法,设置私钥进行签名。
- 更新签名对象的数据(signature.update(data))。
- 调用 signature.sign() 生成签名,并将结果编码为 Base64 字符串返回。
- 用途:数字签名用于验证数据的完整性和发送者身份。例如,发送方用私钥签名数据,接收方用公钥验证签名,确保数据未被篡改且来自可信发送方。
3.2 verify(byte[] data, String publicKey, String sign)
- 功能:使用公钥验证数字签名的有效性。
- 参数:
- data:待验证的原始数据(字节数组)。
- publicKey:Base64 编码的公钥字符串。
- sign:Base64 编码的签名字符串。
- 返回值:布尔值,true 表示签名有效,false 表示无效。
- 实现步骤:
- 将 Base64 编码的公钥字符串解码为字节数组。
- 使用 X509EncodedKeySpec 创建公钥规范(RSA 公钥的标准格式)。
- 通过 KeyFactory 生成公钥对象。
- 初始化 Signature 对象,使用 MD5withRSA 算法,设置公钥进行验证。
- 更新签名对象的数据(signature.update(data))。
- 将 Base64 编码的签名解码为字节数组,调用 signature.verify() 验证签名是否匹配。
- 用途:验证签名以确认数据的完整性和来源。例如,接收方用公钥验证签名,确保数据未被篡改且来自持有对应私钥的发送方。
3.3 encryptByPublicKey(byte[] data, String publicKey)
- 功能:使用公钥加密数据。
- 参数:
- data:待加密的明文数据(字节数组)。
- publicKey:Base64 编码的公钥字符串。
- 返回值:Base64 编码的密文字符串。
- 实现步骤:
- 将 Base64 编码的公钥字符串解码为字节数组。
- 使用 X509EncodedKeySpec 创建公钥规范,通过 KeyFactory 生成公钥对象。
- 初始化 Cipher 对象,使用 RSA 算法,设置为加密模式(Cipher.ENCRYPT_MODE)。
- 由于 RSA 加密明文长度受限(最大 117 字节),对数据进行分段处理:
- 如果剩余数据大于 MAX_ENCRYPT_BLOCK(117 字节),加密 117 字节。
- 否则,加密剩余的所有数据。
- 使用 System.arraycopy 合并每段加密结果到输出数组。
- 将最终加密结果编码为 Base64 字符串返回。
- 用途:公钥加密通常用于安全传输数据,只有持有私钥的接收方才能解密。例如,客户端用服务器的公钥加密敏感数据,服务器用私钥解密。
3.4 decryptByPrivateKey(String encryptedData, String privateKey)
- 功能:使用私钥解密数据。
- 参数:
- encryptedData:Base64 编码的密文字符串。
- privateKey:Base64 编码的私钥字符串。
- 返回值:解密后的明文数据(字节数组)。
- 实现步骤:
- 将 Base64 编码的密文和私钥字符串分别解码为字节数组。
- 使用 PKCS8EncodedKeySpec 创建私钥规范,通过 KeyFactory 生成私钥对象。
- 初始化 Cipher 对象,使用 RSA 算法,设置为解密模式(Cipher.DECRYPT_MODE)。
- 由于 RSA 解密密文长度受限(最大 128 字节),对密文进行分段处理:
- 如果剩余密文大于 MAX_DECRYPT_BLOCK(128 字节),解密 128 字节。
- 否则,解密剩余的所有密文。
- 使用 System.arraycopy 合并每段解密结果到输出数组。
- 返回解密后的明文字节数组。
- 用途:私钥解密用于接收方解密由公钥加密的数据。例如,服务器接收客户端发送的加密数据,用私钥解密。
3.5 encryptByPrivateKey(byte[] data, String privateKey)
- 功能:使用私钥加密数据。
- 参数:
- data:待加密的明文数据(字节数组)。
- privateKey:Base64 编码的私钥字符串。
- 返回值:Base64 编码的密文字符串。
- 实现步骤:与 encryptByPublicKey 类似,但使用私钥进行加密:
- 解码 Base64 私钥,生成私钥对象。
- 初始化 Cipher 为加密模式,使用私钥。
- 分段加密(最大 117 字节),合并结果。
- 返回 Base64 编码的密文。
- 用途:私钥加密通常用于生成数字签名或特殊场景(如身份验证),接收方用公钥解密以验证发送方身份。
3.6 decryptByPublicKey(String encryptedData, String publicKey)
- 功能:使用公钥解密数据。
- 参数:
- encryptedData:Base64 编码的密文字符串。
- publicKey:Base64 编码的公钥字符串。
- 返回值:解密后的明文数据(字节数组)。
- 实现步骤:与 decryptByPrivateKey 类似,但使用公钥进行解密:
- 解码 Base64 密文和公钥,生成公钥对象。
- 初始化 Cipher 为解密模式,使用公钥。
- 分段解密(最大 128 字节),合并结果。
- 返回解密后的明文。
- 用途:公钥解密用于解密由私钥加密的数据,通常与数字签名相关,验证数据的来源和完整性。
- 代码特点与实现细节
4.1 RSA 算法
- RSA 是一种非对称加密算法,使用一对密钥(公钥和私钥):
- 公钥加密,私钥解密的场景用于数据保密。
- 私钥加密,公钥解密的场景用于身份验证或签名。
- RSA 加密/解密需要考虑数据长度限制,因此代码实现了分段处理。
- 密钥格式:
- 公钥使用 X509EncodedKeySpec(X.509 标准)。
- 私钥使用 PKCS8EncodedKeySpec(PKCS#8 标准)。
4.2 分段加密/解密
- RSA 算法对数据块大小有限制(1024 位密钥为 117 字节明文,128 字节密文)。
- 代码通过循环分段处理长数据:
- 加密时按 MAX_ENCRYPT_BLOCK(117)分割。
- 解密时按 MAX_DECRYPT_BLOCK(128)分割。
- 使用 System.arraycopy 动态合并结果。
- 注意:分段加密可能影响性能,对于大文件通常结合对称加密(如 AES)使用 RSA 加密密钥。
- 潜在问题与改进建议
- 签名算法安全性:
- MD5withRSA 使用 MD5 哈希算法,MD5 已知存在碰撞风险,建议升级为 SHA256withRSA 或更高(如 SHA512withRSA)。
- 密钥长度:
- 代码假设 1024 位密钥(128 字节),但未显式校验密钥长度。建议增加对 2048 位或更高密钥的支持,并动态计算 MAX_ENCRYPT_BLOCK 和 MAX_DECRYPT_BLOCK。
- 异常处理:
- 代码抛出通用 Exception,建议细化异常类型(如 InvalidKeyException、BadPaddingException),便于调试。
- 性能优化:
- 分段加密/解密的内存分配(byte[] temp)可能导致频繁 GC,建议使用 ByteArrayOutputStream 优化。
- 输入验证:
- 未对输入(data、privateKey、publicKey)进行空值或格式校验,可能导致运行时异常。建议添加输入验证。
- 填充模式:
- 代码使用默认填充(可能是 RSA/ECB/PKCS1Padding),建议显式指定填充模式以确保跨平台兼容性。
- 实际应用场景
- 司法 API:根据包名(sfkc.thirdapi.judicial),可能用于与司法系统交互,如提交加密的案件数据、验证签名以确认数据来源。
- 数据安全传输:客户端用公钥加密敏感数据(如身份证号、案件详情),服务器用私钥解密。
- 身份验证:私钥签名数据,接收方用公钥验证,确保数据来自可信方。
- 典型流程:
- 客户端用服务器公钥加密数据,发送给服务器。
- 客户端用私钥对数据签名,服务器用客户端公钥验证签名。
- 服务器用私钥解密数据,处理后返回结果。
- 代码示例
以下是一个使用 KeyUtil 类的简单示例:
java
public class KeyUtilExample {
public static void main(String[] args) throws Exception {
// 示例公钥和私钥(Base64 编码,需替换为实际密钥)
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...";
String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJd...";
// 待加密的数据
String data = "Hello, this is a test message!";
byte[] dataBytes = data.getBytes();
// 1. 公钥加密
String encrypted = KeyUtil.encryptByPublicKey(dataBytes, publicKey);
System.out.println("Encrypted: " + encrypted);
// 2. 私钥解密
byte[] decrypted = KeyUtil.decryptByPrivateKey(encrypted, privateKey);
System.out.println("Decrypted: " + new String(decrypted));
// 3. 私钥签名
String signature = KeyUtil.sign(dataBytes, privateKey);
System.out.println("Signature: " + signature);
// 4. 公钥验证签名
boolean verified = KeyUtil.verify(dataBytes, publicKey, signature);
System.out.println("Signature verified: " + verified);
}
}
- 总结
KeyUtil 是一个功能完备的 RSA 加密和签名工具类,适用于需要非对称加密和数字签名的场景。其主要特点包括:
- 支持公钥/私钥加密解密。
- 支持私钥签名和公钥验证。
- 实现分段加密/解密,处理长数据。
- 使用 Base64 编码便于密钥和数据的传输。
改进方向:
- 升级签名算法到更安全的 SHA-256。
- 增强输入验证和异常处理。
- 优化分段加密的性能。
- 显式指定填充模式。