业务标准方案:AES 加密业务明文,RSA 加密 AES 的密钥 为什么这么做:RSA 加密长度受限,适合加密短数据(AES 密钥、口令);AES 对称加密速度快,适合任意长度业务报文。 流程:
发送方:随机生成 AES 密钥 → AES 加密业务数据 → RSA (公钥) 加密 AES 密钥
传输:密文 + RSA 加密后的 AES 密钥 + RSA 签名(可选,防篡改、身份校验)
接收方:RSA (私钥) 解密拿到 AES 密钥 → AES 解密业务密文;同时验签校验报文完整性
算法选型(生产推荐)
AES:AES-256/CBC/PKCS5Padding(CBC 模式,需要 IV 向量)
RSA:RSA/ECB/OAEPWithSHA-256AndMGF1Padding
签名:SHA256withRSA
注意:JDK 默认 AES 只支持 128 位;使用 AES-256 需要安装 JCE 无限制权限包,或者改用 JDK17+。下面示例默认 AES-128。
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* RSA+AES混合加密工具
* 业务模型:AES加密报文,RSA加密AES密钥;附加RSA签名防篡改
*/
public class RsaAesUtil {
// ===================== RSA 常量 =====================
private static final int RSA_KEY_SIZE = 2048;
private static final String RSA_ALGORITHM = "RSA";
private static final String RSA_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
private static final String SIGN_ALGORITHM = "SHA256withRSA";
// ===================== AES 常量 =====================
private static final String AES_ALGORITHM = "AES";
private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
// AES 128位密钥 = 16字节;256位=32字节
private static final int AES_KEY_LEN = 16;
// CBC模式IV向量固定16字节
private static final int AES_IV_LEN = 16;
public static void main(String[] args) throws Exception {
// 1. 生成RSA密钥对(实际项目:服务端保存私钥,对外分发公钥)
KeyPair rsaKeyPair = generateRsaKeyPair();
PublicKey rsaPublicKey = rsaKeyPair.getPublic();
PrivateKey rsaPrivateKey = rsaKeyPair.getPrivate();
String pubKeyBase64 = Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded());
String priKeyBase64 = Base64.getEncoder().encodeToString(rsaPrivateKey.getEncoded());
System.out.println("RSA公钥Base64:\n" + pubKeyBase64);
System.out.println("RSA私钥Base64:\n" + priKeyBase64);
// 原始业务报文,可以很长
String originMsg = "这是业务报文,支持超长文本!!测试RSA+AES混合加密,同时附加RSA签名保证报文不可篡改";
System.out.println("\n原始报文:" + originMsg);
// ============ 发送方:加密 + 签名 ============
// 生成随机AES密钥 + IV向量
byte[] aesKey = generateAesKey();
byte[] aesIv = generateAesIv();
// AES加密业务报文
String aesEncryptMsg = aesEncrypt(originMsg, aesKey, aesIv);
// RSA公钥加密AES密钥
String aesKeyEncrypted = rsaEncrypt(Base64.getEncoder().encodeToString(aesKey), pubKeyBase64);
// RSA私钥对【原始报文】签名
String sign = sign(originMsg, priKeyBase64);
System.out.println("\nAES加密报文:" + aesEncryptMsg);
System.out.println("RSA加密后的AES密钥:" + aesKeyEncrypted);
System.out.println("报文签名:" + sign);
// ============ 接收方:验签 + 解密 ============
// 第一步:先验签,校验报文是否被篡改(安全最佳实践:验签失败直接拒绝解密)
boolean verifyOk = verify(originMsg, sign, pubKeyBase64);
System.out.println("\n验签结果:" + verifyOk);
if (!verifyOk) {
System.err.println("报文被篡改!");
return;
}
// RSA私钥解密,拿到AES密钥字符串,再base64解码还原byte[]
String aesKeyBase64 = rsaDecrypt(aesKeyEncrypted, priKeyBase64);
byte[] realAesKey = Base64.getDecoder().decode(aesKeyBase64);
// AES解密报文
String decryptMsg = aesDecrypt(aesEncryptMsg, realAesKey, aesIv);
System.out.println("解密后报文:" + decryptMsg);
// 篡改测试:修改报文,验签失败
String tamperMsg = "被人篡改后的业务报文";
boolean tamperVerify = verify(tamperMsg, sign, pubKeyBase64);
System.out.println("篡改报文验签结果:" + tamperVerify);
}
// region RSA 工具方法
public static KeyPair generateRsaKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
generator.initialize(RSA_KEY_SIZE, new SecureRandom());
return generator.generateKeyPair();
}
/** RSA公钥加密 */
public static String rsaEncrypt(String plainText, String publicKeyBase64) throws Exception {
byte[] pubBytes = Base64.getDecoder().decode(publicKeyBase64);
X509EncodedKeySpec spec = new X509EncodedKeySpec(pubBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(spec);
Cipher cipher = Cipher.getInstance(RSA_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] data = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(data);
}
/** RSA私钥解密 */
public static String rsaDecrypt(String cipherBase64, String privateKeyBase64) throws Exception {
byte[] priBytes = Base64.getDecoder().decode(privateKeyBase64);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(priBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(spec);
Cipher cipher = Cipher.getInstance(RSA_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] data = cipher.doFinal(Base64.getDecoder().decode(cipherBase64));
return new String(data, StandardCharsets.UTF_8);
}
/** RSA私钥签名 */
public static String sign(String content, String privateKeyBase64) throws Exception {
byte[] priBytes = Base64.getDecoder().decode(privateKeyBase64);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(priBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(spec);
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initSign(privateKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
byte[] signBytes = signature.sign();
return Base64.getEncoder().encodeToString(signBytes);
}
/** RSA公钥验签 */
public static boolean verify(String content, String signBase64, String publicKeyBase64) throws Exception {
byte[] pubBytes = Base64.getDecoder().decode(publicKeyBase64);
X509EncodedKeySpec spec = new X509EncodedKeySpec(pubBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(spec);
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initVerify(publicKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.getDecoder().decode(signBase64));
}
// endregion
// region AES 工具方法
/** 生成AES随机密钥 16字节 */
public static byte[] generateAesKey() {
byte[] key = new byte[AES_KEY_LEN];
new SecureRandom().nextBytes(key);
return key;
}
/** 生成AES CBC随机IV向量 16字节 */
public static byte[] generateAesIv() {
byte[] iv = new byte[AES_IV_LEN];
new SecureRandom().nextBytes(iv);
return iv;
}
/** AES CBC加密 返回Base64密文 */
public static String aesEncrypt(String plainText, byte[] aesKey, byte[] iv) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(aesKey, AES_ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encryptBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptBytes);
}
/** AES CBC解密 */
public static String aesDecrypt(String cipherBase64, byte[] aesKey, byte[] iv) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(aesKey, AES_ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decryptBytes = cipher.doFinal(Base64.getDecoder().decode(cipherBase64));
return new String(decryptBytes, StandardCharsets.UTF_8);
}
// endregion
}
传输报文结构建议(前后端 / 接口交互)
JSON 样例:
{
"data": "AES加密后的业务报文Base64",
"aesKey": "RSA公钥加密后的AES密钥Base64",
"iv": "AES的IV向量Base64",
"sign": "RSA签名Base64"
}
注意:IV 不需要加密,明文传输即可,IV 只用于防相同明文产生相同密文。
生产重点优化点
异常封装:代码现在抛出原始 Exception,生产建议自定义加密异常,区分:密钥错误、解密失败、验签失败。
签名对象选择
方案 A:对原始明文签名(示例当前方案)
方案 B:对
data+aesKey+iv拼接后签名(推荐接口场景,防止攻击者替换密文但明文不变)
密钥管理:RSA 私钥严禁放在代码 / 配置文件,使用 KMS、密钥托管;不要硬编码密钥。
AES 模式选型:GCM 模式(AES/GCM)自带完整性校验,可以替代 RSA 签名,减少一次 RSA 运算,性能更好。我可以给你改成 AES-GCM 版本。
编码:全部强制
StandardCharsets.UTF_8,避免不同操作系统编码错乱。
本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。
原文链接
欢迎访问