特性:
AES:AES-256-GCM(带认证,同时实现加密 + 完整性校验,比 CBC 更安全,自带 IV、AuthTag)
RSA:
RSA/ECB/OAEPWithSHA-256AndMGF1Padding加密 AES 密钥签名:SHA256withRSA(可选,可单独对业务数据签名,防抵赖)
输出统一封装成对象,序列化用 Base64,方便接口传输(JSON)
JDK 原生 API,无第三方依赖;JDK8 注意:AES-256 需要额外安装无限制权限包;JDK9 + 默认支持
流程:
发送方:随机生成 AES 密钥 + IV → AES-GCM 加密业务数据 → RSA 加密 AES 密钥 → (可选 RSA 签名原始报文) 接收方:RSA 解密拿到 AES 密钥 → AES-GCM 解密数据(GCM 会自动校验篡改)
说明:GCM 模式会输出
密文 + tag,tag 是认证标签,解密失败 = 数据被篡改 / 密钥错误。
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* RSA + AES-GCM 混合加密工具类(生产参考模型)
* AES: AES-256-GCM
* RSA: RSA/ECB/OAEPWithSHA-256AndMGF1Padding (2048bit)
*/
public class RsaAesMixUtil {
// ========= 常量定义 =========
private static final String RSA_ALG = "RSA";
private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
private static final String AES_ALG = "AES";
private static final String AES_CIPHER = "AES/GCM/NoPadding";
// GCM 参数:tag长度 128bit,推荐IV固定12字节(NIST推荐)
private static final int GCM_TAG_LEN_BIT = 128;
private static final int GCM_IV_LEN = 12;
private static final int AES_KEY_SIZE = 256;
private static final int RSA_KEY_SIZE = 2048;
private static final String SIGN_ALG = "SHA256withRSA";
/**
* 加密结果实体,接口传输转JSON
*/
public static class EncryptResult {
// RSA加密后的AES密钥(Base64)
public String encryptedAesKey;
// AES IV(Base64)
public String iv;
// AES加密业务密文(Base64)
public String cipherText;
// GCM auth tag(Base64)
public String tag;
// 可选:原始报文签名(Base64),不需要签名可以去掉
public String sign;
}
/**
* 生成RSA密钥对
*/
public static KeyPair generateRsaKeyPair() throws Exception {
KeyPairGenerator gen = KeyPairGenerator.getInstance(RSA_ALG);
gen.initialize(RSA_KEY_SIZE);
return gen.generateKeyPair();
}
/**
* 生成AES 256密钥
*/
public static SecretKey generateAesKey() throws Exception {
KeyGenerator gen = KeyGenerator.getInstance(AES_ALG);
gen.init(AES_KEY_SIZE);
return gen.generateKey();
}
/**
* RSA公钥加密(用来加密AES密钥)
*/
public static byte[] rsaEncrypt(byte[] data, PublicKey pubKey) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return cipher.doFinal(data);
}
/**
* RSA私钥解密(还原AES密钥)
*/
public static byte[] rsaDecrypt(byte[] cipherData, PrivateKey priKey) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
cipher.init(Cipher.DECRYPT_MODE, priKey);
return cipher.doFinal(cipherData);
}
/**
* AES-GCM加密
* @param plain 原始业务字节
* @param aesKey AES密钥
* @return [cipherBytes, tagBytes]
*/
public static byte[][] aesGcmEncrypt(byte[] plain, SecretKey aesKey) throws Exception {
// 随机IV
byte[] iv = new byte[GCM_IV_LEN];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
Cipher cipher = Cipher.getInstance(AES_CIPHER);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LEN_BIT, iv);
cipher.init(Cipher.ENCRYPT_MODE, aesKey, spec);
byte[] cipherText = cipher.doFinal(plain);
byte[] tag = cipher.getIV();
// GCM模式:doFinal输出=密文+tag,拆分:最后tag长度是16字节(128bit)
byte[] realCipher = new byte[cipherText.length - (GCM_TAG_LEN_BIT / 8)];
byte[] authTag = new byte[GCM_TAG_LEN_BIT / 8];
System.arraycopy(cipherText, 0, realCipher, 0, realCipher.length);
System.arraycopy(cipherText, realCipher.length, authTag, 0, authTag.length);
return new byte[][]{iv, realCipher, authTag};
}
/**
* AES-GCM解密(自动校验tag,篡改直接抛异常)
*/
public static byte[] aesGcmDecrypt(byte[] cipherBytes, byte[] iv, byte[] tag, SecretKey aesKey) throws Exception {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LEN_BIT, iv);
cipher.init(Cipher.DECRYPT_MODE, aesKey, spec);
// 拼接密文+tag
byte[] input = new byte[cipherBytes.length + tag.length];
System.arraycopy(cipherBytes, 0, input, 0, cipherBytes.length);
System.arraycopy(tag, 0, input, cipherBytes.length, tag.length);
return cipher.doFinal(input);
}
/**
* RSA私钥签名
*/
public static byte[] sign(byte[] data, PrivateKey priKey) throws Exception {
Signature signature = Signature.getInstance(SIGN_ALG);
signature.initSign(priKey);
signature.update(data);
return signature.sign();
}
/**
* RSA公钥验签
*/
public static boolean verify(byte[] data, byte[] signBytes, PublicKey pubKey) throws Exception {
Signature signature = Signature.getInstance(SIGN_ALG);
signature.initVerify(pubKey);
signature.update(data);
return signature.verify(signBytes);
}
// ========= 密钥序列化工具(Base64互转) =========
public static String pub2Base64(PublicKey pub) {
return Base64.getEncoder().encodeToString(pub.getEncoded());
}
public static String pri2Base64(PrivateKey pri) {
return Base64.getEncoder().encodeToString(pri.getEncoded());
}
public static PublicKey base64ToPub(String base64) throws Exception {
byte[] b = Base64.getDecoder().decode(base64);
X509EncodedKeySpec spec = new X509EncodedKeySpec(b);
return KeyFactory.getInstance(RSA_ALG).generatePublic(spec);
}
public static PrivateKey base64ToPri(String base64) throws Exception {
byte[] b = Base64.getDecoder().decode(base64);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b);
return KeyFactory.getInstance(RSA_ALG).generatePrivate(spec);
}
// ========= 对外统一加密入口 =========
public static EncryptResult encrypt(String plainText, PublicKey receiverPubKey, PrivateKey senderPriKey) throws Exception {
EncryptResult result = new EncryptResult();
byte[] plainBytes = plainText.getBytes();
// 1. 随机AES密钥
SecretKey aesKey = generateAesKey();
// 2. AES加密业务数据
byte[][] gcmRet = aesGcmEncrypt(plainBytes, aesKey);
byte[] iv = gcmRet[0];
byte[] cipher = gcmRet[1];
byte[] tag = gcmRet[2];
// 3. RSA加密AES密钥(用接收方公钥)
byte[] encryptedAesKey = rsaEncrypt(aesKey.getEncoded(), receiverPubKey);
// 4. 签名原始报文(发送方私钥,可选)
byte[] signBytes = sign(plainBytes, senderPriKey);
result.encryptedAesKey = Base64.getEncoder().encodeToString(encryptedAesKey);
result.iv = Base64.getEncoder().encodeToString(iv);
result.cipherText = Base64.getEncoder().encodeToString(cipher);
result.tag = Base64.getEncoder().encodeToString(tag);
result.sign = Base64.getEncoder().encodeToString(signBytes);
return result;
}
// ========= 对外统一解密入口 =========
public static String decrypt(EncryptResult encryptResult, PrivateKey receiverPriKey, PublicKey senderPubKey) throws Exception {
// 1. Base64解码
byte[] encryptedAesKey = Base64.getDecoder().decode(encryptResult.encryptedAesKey);
byte[] iv = Base64.getDecoder().decode(encryptResult.iv);
byte[] cipher = Base64.getDecoder().decode(encryptResult.cipherText);
byte[] tag = Base64.getDecoder().decode(encryptResult.tag);
byte[] signBytes = Base64.getDecoder().decode(encryptResult.sign);
// 2. RSA解密拿到AES密钥
byte[] aesKeyRaw = rsaDecrypt(encryptedAesKey, receiverPriKey);
SecretKey aesKey = new SecretKeySpec(aesKeyRaw, AES_ALG);
// 3. AES GCM解密
byte[] plainBytes = aesGcmDecrypt(cipher, iv, tag, aesKey);
// 4. 验签(校验发送方身份+报文未篡改)
boolean pass = verify(plainBytes, signBytes, senderPubKey);
if (!pass) {
throw new SecurityException("签名校验失败,数据来源不可信或报文被篡改");
}
return new String(plainBytes);
}
public static void main(String[] args) throws Exception {
// ===== 角色:接收方(服务端)RSA密钥对,用来解密AES密钥 =====
KeyPair receiverKp = generateRsaKeyPair();
PublicKey receiverPub = receiverKp.getPublic();
PrivateKey receiverPri = receiverKp.getPrivate();
// ===== 角色:发送方(客户端)RSA密钥对,用来签名 =====
KeyPair senderKp = generateRsaKeyPair();
PublicKey senderPub = senderKp.getPublic();
PrivateKey senderPri = senderKp.getPrivate();
String originMsg = "这是业务明文,可以很长,RSA只加密AES密钥";
System.out.println("原始报文:" + originMsg);
// 加密
EncryptResult encryptResult = encrypt(originMsg, receiverPub, senderPri);
System.out.println("\nencryptedAesKey:" + encryptResult.encryptedAesKey);
System.out.println("iv:" + encryptResult.iv);
System.out.println("cipherText:" + encryptResult.cipherText);
System.out.println("tag:" + encryptResult.tag);
System.out.println("sign:" + encryptResult.sign);
// 解密
String decrypted = decrypt(encryptResult, receiverPri, senderPub);
System.out.println("\n解密结果:" + decrypted);
// 模拟篡改测试:修改cipherText,GCM解密直接抛异常
// encryptResult.cipherText = "xxx";
// decrypt(encryptResult, receiverPri, senderPub);
}
}
架构说明(接口交互)
客户端(发送方)
1. 随机AES-256密钥
2. AES-GCM加密业务报文
3. 【接收方公钥】加密AES密钥(保证只有接收方能解开AES key)
4. 【发送方私钥】对原始报文签名(证明是我发的,不可抵赖)
→ 传给后端JSON:{encryptedAesKey,iv,cipherText,tag,sign}
服务端(接收方)
1. 自己私钥解密得到AES密钥
2. AES-GCM解密(tag校验,防篡改)
3. 使用【发送方公钥】验签
4. 验签通过才使用业务数据
⚠️ 生产落地清单(重点)
密钥分发
接收方公钥公开给调用方;接收方私钥严格保管,放 KMS,不要写配置文件
发送方私钥仅自己持有;发送方公钥提前给接收方(或者放证书)
JDK 版本兼容
JDK8:AES-256 会抛
IllegalKeySizeException,需要安装 JCE Unlimited Strength Jurisdiction Policy FilesJDK 1.8.161+ / JDK9+ 默认支持 256 位 AES,不需要额外包
GCM 注意:IV 必须每次随机,不要复用同一个 IV + 同一个 AES 密钥,会严重破坏安全性
异常处理:
GCM 解密失败、验签失败统一捕获,不要对外返回详细错误堆栈,只返回 “数据非法”
不要区分是解密失败还是验签失败(防止给攻击者提供侧信道信息)
关于签名:
如果只做 HTTPS 传输、不需要业务层不可抵赖,可以把签名逻辑删掉,减少性能损耗
需要更高安全的签名,把
SHA256withRSA替换成SHA256withRSAandMGF1(RSASSA-PSS)
长度:AES 可以加密超大文本,不受 RSA 长度限制;RSA 只固定加密 32 字节 AES 密钥
编码:接口传输全部 Base64,不要直接传二进制数组
扩展方案选择
如果对接第三方老系统(只支持 PKCS1):RSA 填充改为
RSA/ECB/PKCS1Padding如果需要国密:替换成 SM2 + SM4
高并发场景:AES 生成、SecureRandom 可以做少量池化优化(但 SecureRandom 本身不要随便改)