小易说IT 小易说IT

Java RSA+AES 混合加密完整工具类

业务标准方案:AES 加密业务明文,RSA 加密 AES 的密钥 为什么这么做:RSA 加密长度受限,适合加密短数据(AES 密钥、口令);AES 对称加密速度快,适合任意长度业务报文。 流程:

  1. 发送方:随机生成 AES 密钥 → AES 加密业务数据 → RSA (公钥) 加密 AES 密钥

  2. 传输:密文 + RSA 加密后的 AES 密钥 + RSA 签名(可选,防篡改、身份校验)

  3. 接收方: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 只用于防相同明文产生相同密文。

生产重点优化点

  1. 异常封装:代码现在抛出原始 Exception,生产建议自定义加密异常,区分:密钥错误、解密失败、验签失败。

  2. 签名对象选择

    • 方案 A:对原始明文签名(示例当前方案)

    • 方案 B:对 data+aesKey+iv 拼接后签名(推荐接口场景,防止攻击者替换密文但明文不变)

  3. 密钥管理:RSA 私钥严禁放在代码 / 配置文件,使用 KMS、密钥托管;不要硬编码密钥。

  4. AES 模式选型:GCM 模式(AES/GCM)自带完整性校验,可以替代 RSA 签名,减少一次 RSA 运算,性能更好。我可以给你改成 AES-GCM 版本。

  5. 编码:全部强制StandardCharsets.UTF_8,避免不同操作系统编码错乱。


本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。

原文链接 https://www.yijunzhao.cc/archives/java-rsa-aes-hybrid-encryption-utility-class

欢迎访问 https://www.yijunzhao.cc/

https://www.yijunzhao.cc/