Java安全通信:RSA签名 + AES混合加密详解

1-解决问题

在网络通信中,其实本质就是保证两个问题:

  • 数据不能被别人看懂:用加密
  • 数据确实是你发的:用签名

一般内部系统用token或者网关认证,外部系统的对接,会使用签名来校验来源的可靠性和真实性

2-对称加密

  • 特点:一把钥匙(key)加密 + 解密
bash 复制代码
明文 --(key加密)--> 密文
密文 --(key解密)--> 明文
  • 优点:

    • 速度快(适合大数据)
    • 实现简单
  • 缺点:加解密都是一个秘钥,系统间约定,但是秘钥的传输,可能泄露

  • 常见算法:

    • AES(最常用,基本行业标准)
    • DES(已淘汰)
    • 3DES(过渡方案)
  • 基础代码:

java 复制代码
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey key = keyGen.generateKey();

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);

byte[] encrypted = cipher.doFinal("hello".getBytes());

3-非对称加密

  • 特点:
bash 复制代码
公钥(public key)------可以公开
私钥(private key)------必须保密
  • 常见算法:
    • RSA(最常用)
    • SM2(国产)
    • ECC(更先进,性能更好)

3-2.签名机制

签名流程:

发送方:

复制代码
原文 → hash → 用私钥加密 → 签名

接收方:

复制代码
签名 → 公钥解密 → 得到hash
原文 → hash

对比两个hash

hash一致,说明数据没被篡改

常见算法

  • MD5(不安全)
  • SHA256(常用)
  • RSA + SHA256(标准组合)

3-3.流程

请求:A → B

复制代码
A:
  用 B 的公钥加密数据
  (可选)用 A 的私钥签名

→ 发送

B:
  用自己的私钥解密
  用 A 的公钥验签(如果有签名)

响应:B → A

复制代码
B:
  用 A 的公钥加密(可选)
  用自己的私钥签名

→ 返回

A:
  用自己的私钥解密(如果加密)
  用 B 的公钥验签

口诀:

  • 加密:给谁 → 用谁公钥
  • 解密:谁收 → 谁私钥
  • 签名:谁发 → 谁私钥
  • 验签:验谁 → 用谁公钥
bash 复制代码
# 加密
我 → 用对方公钥加密 → 对方私钥解密
# 签名
我 → 用自己私钥签名 → 对方用我公钥验证

3-4.基础实现

RSA 直接加密数据 + RSA 做签名

java 复制代码
import javax.crypto.Cipher;
import java.security.*;
import java.util.Base64;

public class RSADemo {

    public static void main(String[] args) throws Exception {

        // ====== 1. 模拟 A 和 B 各自生成密钥对 ======
        KeyPair A = generateKeyPair();
        KeyPair B = generateKeyPair();
        // A的公私钥
        PublicKey A_pub = A.getPublic();
        PrivateKey A_pri = A.getPrivate();
        // B的公私钥
        PublicKey B_pub = B.getPublic();
        PrivateKey B_pri = B.getPrivate();

        // ====== 2. A -> B:发送数据(加密 + 签名) ======
        String original = "hello,我是A";

        // 用 B 的公钥加密
        String encrypted = encrypt(original, B_pub);

        // 用 A 的私钥签名
        String signature = sign(original, A_pri);

        System.out.println("A发送密文: " + encrypted);
        System.out.println("A发送签名: " + signature);

        // ====== 3. B 接收数据 ======

        // 用自己的私钥解密
        String decrypted = decrypt(encrypted, B_pri);

        // 用 A 的公钥验签
        boolean verify = verify(decrypted, signature, A_pub);

        System.out.println("B解密后: " + decrypted);
        System.out.println("B验签结果: " + verify);

    }

    // ====== 生成密钥对 ======
    public static KeyPair generateKeyPair() throws Exception {
        // 1. KeyPairGenerator秘钥生成器,使用RSA算法
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        // 2. 初始化生成器,设置密钥长度为 2048 位
        generator.initialize(2048);
        // 3. 生成密钥对(包含公钥和私钥)
        return generator.generateKeyPair();
    }

    // ====== 加密(公钥) ======
    public static String encrypt(String data, PublicKey publicKey) throws Exception {
        // 1. 创建一个 Cipher(密码器),指定算法为 RSA
        Cipher cipher = Cipher.getInstance("RSA");
        // 2. 初始化为加密模式,并传入公钥	
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        // 3. 用公钥加密数据(先转成字节数组)
        byte[] bytes = cipher.doFinal(data.getBytes());
        // 4. 把加密后的字节转成 Base64 字符串,方便存储和传输
        return Base64.getEncoder().encodeToString(bytes);
    }

    // ====== 解密(私钥) ======
    public static String decrypt(String data, PrivateKey privateKey) throws Exception {
        // 1. 创建一个 Cipher(密码器),指定算法为 RSA
        Cipher cipher = Cipher.getInstance("RSA");
        // 2. 初始化为解密模式,并传入私钥
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        // 3. 先把 Base64 字符串解码成字节数组
        byte[] bytes = Base64.getDecoder().decode(data);
        // 4. 用私钥解密字节数组,得到原始数据
        return new String(cipher.doFinal(bytes));
    }

    // ====== 签名(私钥) ======
    // SHA256withRSA:表示先用 SHA-256 做哈希,再用 RSA 私钥对哈希结果签名
    public static String sign(String data, PrivateKey privateKey) throws Exception {
        // 1. 创建 Signature 对象,指定算法为 SHA256withRSA
        Signature signature = Signature.getInstance("SHA256withRSA");
        // 2. 初始化为"签名模式",使用私钥
        signature.initSign(privateKey);
        // 3. 把要签名的数据输入进去(update 就是"喂数据"),可以多个update多次添加,这样就支持大文件了
        signature.update(data.getBytes());
        // 4. 生成签名字节数组
        byte[] signBytes = signature.sign();
        // 5. 转成 Base64 字符串,方便传输
        return Base64.getEncoder().encodeToString(signBytes);
    }

    // ====== 验签(公钥) ======
    public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
        // 1. 创建 Signature 对象,指定算法为 SHA256withRSA
        Signature signature = Signature.getInstance("SHA256withRSA");
        // 2. 初始化为"验证模式",使用公钥
        signature.initVerify(publicKey);
        // 3. 把原始数据输入进去
        signature.update(data.getBytes());
        // 4. 用公钥验证签名是否匹配
        return signature.verify(Base64.getDecoder().decode(sign));
    }
}
  • 现在是RSA 直接加密数据 + RSA 做签名

  • 这里加解密部分使用了RSA,但是因为RSA的长度问题以及性能问题,一般加解密这里会切换为对称加密,也就是混合加密

4-混合加密

  • 原来是RSA加密数据
  • 现在是AES加密数据,然后RSA加密AES key(这个加密过的 AES Key也传递就过去,规避性能问题),最后解密出AES key,最后使用AES解密原文
  • 签名部分最好也用加密后的密文签
java 复制代码
package com.personal.downloadbooks.demos;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Base64;

public class RSADemo {

    public static void main(String[] args) throws Exception {

        // ====== 1. 模拟 A 和 B 各自生成密钥对 ======
        KeyPair A = generateKeyPair();
        KeyPair B = generateKeyPair();
        // A的公私钥
        PublicKey A_pub = A.getPublic();
        PrivateKey A_pri = A.getPrivate();
        // B的公私钥
        PublicKey B_pub = B.getPublic();
        PrivateKey B_pri = B.getPrivate();

        // ====== 2. A -> B:发送数据(加密 + 签名) ======
        String original = "hello,我是A";
        // 生成 AES key
        SecretKey aesKey = generateAESKey();
        // 用 AES 加密数据
        String encryptedData = aesEncrypt(original, aesKey);
        // 用 B公钥 加密 AES key
        String encryptedKey = encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), B_pub);
        // 签名(建议签名密文)
        String signature = sign(encryptedData, A_pri);
        System.out.println("加密数据: " + encryptedData);
        System.out.println("加密AES key: " + encryptedKey);
        System.out.println("签名: " + signature);

        // ====== 3. B 接收数据 ======
        // 用 B私钥 解密 AES key
        String aesKeyStr = decrypt(encryptedKey, B_pri);
        byte[] keyBytes = Base64.getDecoder().decode(aesKeyStr);
        SecretKey originAesKey = new SecretKeySpec(keyBytes, "AES");
        // 用 AES 解密数据
        String decrypted = aesDecrypt(encryptedData, originAesKey);
        // 验签
        boolean verify = verify(encryptedData, signature, A_pub);

        System.out.println("解密后: " + decrypted);
        System.out.println("验签结果: " + verify);

    }

    // ====== 生成密钥对 ======
    public static KeyPair generateKeyPair() throws Exception {
        // 1. KeyPairGenerator秘钥生成器,使用RSA算法
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        // 2. 初始化生成器,设置密钥长度为 2048 位
        generator.initialize(2048);
        // 3. 生成密钥对(包含公钥和私钥)
        return generator.generateKeyPair();
    }

    // ====== 加密(公钥) ======
    public static String encrypt(String data, PublicKey publicKey) throws Exception {
        // 1. 创建一个 Cipher(密码器),指定算法为 RSA
        Cipher cipher = Cipher.getInstance("RSA");
        // 2. 初始化为加密模式,并传入公钥	
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        // 3. 用公钥加密数据(先转成字节数组)
        byte[] bytes = cipher.doFinal(data.getBytes());
        // 4. 把加密后的字节转成 Base64 字符串,方便存储和传输
        return Base64.getEncoder().encodeToString(bytes);
    }

    // ====== 解密(私钥) ======
    public static String decrypt(String data, PrivateKey privateKey) throws Exception {
        // 1. 创建一个 Cipher(密码器),指定算法为 RSA
        Cipher cipher = Cipher.getInstance("RSA");
        // 2. 初始化为解密模式,并传入私钥
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        // 3. 先把 Base64 字符串解码成字节数组
        byte[] bytes = Base64.getDecoder().decode(data);
        // 4. 用私钥解密字节数组,得到原始数据
        return new String(cipher.doFinal(bytes));
    }

    // ====== 签名(私钥) ======
    // SHA256withRSA:表示先用 SHA-256 做哈希,再用 RSA 私钥对哈希结果签名
    public static String sign(String data, PrivateKey privateKey) throws Exception {
        // 1. 创建 Signature 对象,指定算法为 SHA256withRSA
        Signature signature = Signature.getInstance("SHA256withRSA");
        // 2. 初始化为"签名模式",使用私钥
        signature.initSign(privateKey);
        // 3. 把要签名的数据输入进去(update 就是"喂数据"),可以多个update多次添加,这样就支持大文件了
        signature.update(data.getBytes());
        // 4. 生成签名字节数组
        byte[] signBytes = signature.sign();
        // 5. 转成 Base64 字符串,方便传输
        return Base64.getEncoder().encodeToString(signBytes);
    }

    // ====== 验签(公钥) ======
    public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
        // 1. 创建 Signature 对象,指定算法为 SHA256withRSA
        Signature signature = Signature.getInstance("SHA256withRSA");
        // 2. 初始化为"验证模式",使用公钥
        signature.initVerify(publicKey);
        // 3. 把原始数据输入进去
        signature.update(data.getBytes());
        // 4. 用公钥验证签名是否匹配
        return signature.verify(Base64.getDecoder().decode(sign));
    }

    public static SecretKey generateAESKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(128); // 128位足够
        return keyGen.generateKey();
    }

    public static String aesEncrypt(String data, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encrypted = cipher.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public static String aesDecrypt(String data, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decoded = Base64.getDecoder().decode(data);
        return new String(cipher.doFinal(decoded));
    }
}

Cipher.getInstance("AES"),默认是ECB模式,不安全,后续需要改为AES/CBC/PKCS5Padding或者AES/GCM/NoPadding

PS:EBC会被看出来,通过比较密文块,推断出明文的重复结构。相当于默认的AES,假如明文AAAA加密后变成BBBB,然后后续再加密明文AAAA,还会是BBBB,后续可以被推断出来

4-1.CBC混合加密

4-1-1.Cipher

常用写法:Cipher.getInstance("AES/CBC/PKCS5Padding");

斜杠区别的是:

复制代码
算法 / 工作模式 / 填充方式
  • AES:用什么算法
  • CBC:怎么加密数据(模式)
  • PKCS5Padding:数据不够怎么补齐
4-1-2.CBC

Cipher Block Chaining的缩写,决定了 AES 如何处理数据块

  • AES 是分块加密的(每次处理 16 字节)
  • ECB 模式 下,每个块独立加密,相同的明文块 → 相同的密文块,不安全
  • CBC 模式 下,每个块的加密结果会依赖前一个块的密文
    • 第一个块会和一个随机生成的 IV(初始化向量) 做异或运算
    • 后续块会和前一个密文块做异或,再加密
  • 这样即使明文重复,密文也会不同,安全性大大提高

CBC 的关键点:需要一个随机 IV,每次加密都不同。IV 不需要保密,但必须随机

4-1-3.CBC大致理解

一个 byte = 8位的计算,示例:

bash 复制代码
P1[0] = 0x41  → 01000001   ('A')
IV[0] = 0x12  → 00010010
# 做 XOR:
01000001
⊕00010010
---------
01010011  → 0x53 ('S')

换到CBC的情况下,16子节的数组,就是两个byte数组,逐个位置做 XOR:

bash 复制代码
明文块 P1(16字节):
[ p0, p1, p2, ..., p15 ]

IV(16字节):
[ iv0, iv1, iv2, ..., iv15 ]

然后我们这个IV是随机生成的,就相当于多了一个扰乱因子,后续就是链式的情况了:

bash 复制代码
P1 ⊕ IV  → AES → C1
P1 ⊕ C1  → AES → C2
  • 因为解密也需要IV,IV本身不需要保密,只要保证随机性和完整性即可
  • 传递的时候转成Base64直接传就行
  • 或者将IV拼在密文前面,例如:[IV][密文],传递过去让接收方切第一个16子节作为IV
4-1-4.PKCS5Padding

这是 填充方式,因为 AES 必须处理固定大小的块(16 字节)

  • 如果明文不是 16 的倍数,就要补齐
  • PKCS5Padding 的规则是:补多少字节,就用多少值来填充
    • 比如明文差 3 字节,就补 0x03 0x03 0x03
    • 差 5 字节,就补 0x05 0x05 0x05 0x05 0x05
  • 解密时,AES 会自动去掉这些填充
4-1-5.基本代码
java 复制代码
    package com;

    import javax.crypto.Cipher;
    import javax.crypto.KeyGenerator;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.*;
    import java.util.Base64;

    public class RSADemo {

        public static void main(String[] args) throws Exception {

            // ====== 1. 模拟 A 和 B 各自生成密钥对 ======
            KeyPair A = generateKeyPair();
            KeyPair B = generateKeyPair();
            // A的公私钥
            PublicKey A_pub = A.getPublic();
            PrivateKey A_pri = A.getPrivate();
            // B的公私钥
            PublicKey B_pub = B.getPublic();
            PrivateKey B_pri = B.getPrivate();

            // ====== 2. A -> B:发送数据(加密 + 签名) ======
            String original = "hello,我是A";
            // 生成 AES key
            SecretKey aesKey = generateAESKey();
            // 生成扰动因子IV
            byte[] iv = generateIV();
            // AES加密数据(IV 会拼接在密文前面)
            String encryptedData = aesEncrypt(original, aesKey, iv);
            // 用 B公钥 加密 AES key
            String encryptedKey = encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), B_pub);
            // 签名(建议签名密文)
            String signature = sign(encryptedData, A_pri);
            System.out.println("加密数据: " + encryptedData);
            System.out.println("加密AES key: " + encryptedKey);
            System.out.println("签名: " + signature);

            // ====== 3. B 接收数据 ======
            // 用 B私钥 解密 AES key
            String aesKeyStr = decrypt(encryptedKey, B_pri);
            byte[] keyBytes = Base64.getDecoder().decode(aesKeyStr);
            SecretKey originAesKey = new SecretKeySpec(keyBytes, "AES");
            // 用 AES 解密数据
            String decrypted = aesDecrypt(encryptedData, originAesKey);
            // 验签
            boolean verify = verify(encryptedData, signature, A_pub);

            System.out.println("解密后: " + decrypted);
            System.out.println("验签结果: " + verify);

        }

        // ====== 生成密钥对 ======
        public static KeyPair generateKeyPair() throws Exception {
            // 1. KeyPairGenerator秘钥生成器,使用RSA算法
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            // 2. 初始化生成器,设置密钥长度为 2048 位
            generator.initialize(2048);
            // 3. 生成密钥对(包含公钥和私钥)
            return generator.generateKeyPair();
        }

        // ====== 加密(公钥) ======
        public static String encrypt(String data, PublicKey publicKey) throws Exception {
            // 1. 创建一个 Cipher(密码器),指定算法为 RSA
            Cipher cipher = Cipher.getInstance("RSA");
            // 2. 初始化为加密模式,并传入公钥	
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            // 3. 用公钥加密数据(先转成字节数组)
            byte[] bytes = cipher.doFinal(data.getBytes());
            // 4. 把加密后的字节转成 Base64 字符串,方便存储和传输
            return Base64.getEncoder().encodeToString(bytes);
        }

        // ====== 解密(私钥) ======
        public static String decrypt(String data, PrivateKey privateKey) throws Exception {
            // 1. 创建一个 Cipher(密码器),指定算法为 RSA
            Cipher cipher = Cipher.getInstance("RSA");
            // 2. 初始化为解密模式,并传入私钥
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // 3. 先把 Base64 字符串解码成字节数组
            byte[] bytes = Base64.getDecoder().decode(data);
            // 4. 用私钥解密字节数组,得到原始数据
            return new String(cipher.doFinal(bytes));
        }

        // ====== 签名(私钥) ======
        // SHA256withRSA:表示先用 SHA-256 做哈希,再用 RSA 私钥对哈希结果签名
        public static String sign(String data, PrivateKey privateKey) throws Exception {
            // 1. 创建 Signature 对象,指定算法为 SHA256withRSA
            Signature signature = Signature.getInstance("SHA256withRSA");
            // 2. 初始化为"签名模式",使用私钥
            signature.initSign(privateKey);
            // 3. 把要签名的数据输入进去(update 就是"喂数据"),可以多个update多次添加,这样就支持大文件了
            signature.update(data.getBytes());
            // 4. 生成签名字节数组
            byte[] signBytes = signature.sign();
            // 5. 转成 Base64 字符串,方便传输
            return Base64.getEncoder().encodeToString(signBytes);
        }

        // ====== 验签(公钥) ======
        public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
            // 1. 创建 Signature 对象,指定算法为 SHA256withRSA
            Signature signature = Signature.getInstance("SHA256withRSA");
            // 2. 初始化为"验证模式",使用公钥
            signature.initVerify(publicKey);
            // 3. 把原始数据输入进去
            signature.update(data.getBytes());
            // 4. 用公钥验证签名是否匹配
            return signature.verify(Base64.getDecoder().decode(sign));
        }

        public static SecretKey generateAESKey() throws Exception {
            KeyGenerator keyGen = KeyGenerator.getInstance("AES");
            keyGen.init(128); // 128位足够
            return keyGen.generateKey();
        }

        // ====== AES 加密(CBC + PKCS5Padding) ======
        public static String aesEncrypt(String data, SecretKey key, byte[] iv) throws Exception {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
            byte[] encrypted = cipher.doFinal(data.getBytes());
            // 拼接 IV + 密文,一起传输
            byte[] result = new byte[iv.length + encrypted.length];
            System.arraycopy(iv, 0, result, 0, iv.length);
            System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
            return Base64.getEncoder().encodeToString(result);
        }

        // ====== AES 解密(CBC + PKCS5Padding) ======
        public static String aesDecrypt(String data, SecretKey key) throws Exception {
            byte[] decoded = Base64.getDecoder().decode(data);
            // 拆分出 IV 和密文
            byte[] iv = new byte[16];
            byte[] ciphertext = new byte[decoded.length - 16];
            System.arraycopy(decoded, 0, iv, 0, 16);
            System.arraycopy(decoded, 16, ciphertext, 0, ciphertext.length);

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
            byte[] decrypted = cipher.doFinal(ciphertext);
            return new String(decrypted);
        }

        // ====== 生成随机 IV ======
        public static byte[] generateIV() {
            byte[] iv = new byte[16]; // AES block size = 16 bytes
            new SecureRandom().nextBytes(iv);
            return iv;
        }
    }
4-1-6.调整

主流来说签名不能只签密文部分,最好是更多更全面的信息全部拿进去签,防止有人篡改

比如主流结构:

json 复制代码
{
  "ciphertext": "Base64编码的密文",
  "iv": "Base64编码的IV",
  "encryptedKey": "Base64编码的RSA加密AES密钥",
  "signature": "Base64编码的签名",
  "sender": "A",
  "timestamp": "2026-03-25T11:42:00Z"
}

两种签法:

  • 签整个JSON字符串:通常是整个 JSON(除 signature 字段本身)
  • 签字段拼接(Canonicalization):ciphertext、iv、encryptedKey、timestamp按固定顺序拼接成一个字符串
java 复制代码
package com;

import lombok.Data;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;

public class RSADemo {

    @Data
    public static class SecureRequest {

        /**
         * 应用ID(标识调用方)
         */
        private String appId;

        /**
         * 时间戳(防重放)
         */
        private String timestamp;

        /**
         * 随机串(防重放)
         */
        private String nonce;

        /**
         * 业务数据(AES加密后的Base64)
         */
        private String data;

        /**
         * AES密钥(用对方公钥RSA加密)
         */
        private String encryptKey;

        /**
         * 签名(对指定字段签名)
         */
        private String sign;
    }


    public static void main(String[] args) throws Exception {

        // ====== 1. 模拟 A 和 B 各自生成密钥对 ======
        KeyPair A = generateKeyPair();
        KeyPair B = generateKeyPair();
        // A的公私钥
        PublicKey A_pub = A.getPublic();
        PrivateKey A_pri = A.getPrivate();
        // B的公私钥
        PublicKey B_pub = B.getPublic();
        PrivateKey B_pri = B.getPrivate();

        // ====== 2. A -> B:发送数据(加密 + 签名) ======
        String original = "hello,我是A";
        // 生成 AES key
        SecretKey aesKey = generateAESKey();
        // 生成扰动因子IV
        byte[] iv = generateIV();
        // AES加密数据(IV 会拼接在密文前面)
        String encryptedData = aesEncrypt(original, aesKey, iv);
        // 用 B公钥 加密 AES key
        String encryptedKey = encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), B_pub);
        // 构建请求
        SecureRequest request = new SecureRequest();
        request.setAppId("appA");
        request.setTimestamp(String.valueOf(System.currentTimeMillis()));
        request.setNonce(String.valueOf(new SecureRandom().nextInt()));
        request.setData(encryptedData);
        request.setEncryptKey(encryptedKey);
        // 签名
        String signature = signRequest(request, A_pri);
        request.setSign(signature);

        System.out.println("加密数据: " + encryptedData);
        System.out.println("加密AES key: " + encryptedKey);
        System.out.println("签名: " + signature);

        // ====== 3. B 接收数据 ======
        // 用 B私钥 解密 AES key
        String aesKeyStr = decrypt(encryptedKey, B_pri);
        byte[] keyBytes = Base64.getDecoder().decode(aesKeyStr);
        SecretKey originAesKey = new SecretKeySpec(keyBytes, "AES");
        // 用 AES 解密数据
        String decrypted = aesDecrypt(encryptedData, originAesKey);
        // 验签
        boolean verify = verifyRequest(request, A_pub);

        System.out.println("解密后: " + decrypted);
        System.out.println("验签结果: " + verify);

    }

    // ====== 生成密钥对 ======
    public static KeyPair generateKeyPair() throws Exception {
        // 1. KeyPairGenerator秘钥生成器,使用RSA算法
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        // 2. 初始化生成器,设置密钥长度为 2048 位
        generator.initialize(2048);
        // 3. 生成密钥对(包含公钥和私钥)
        return generator.generateKeyPair();
    }

    // ====== 加密(公钥) ======
    public static String encrypt(String data, PublicKey publicKey) throws Exception {
        // 1. 创建一个 Cipher(密码器),指定算法为 RSA
        Cipher cipher = Cipher.getInstance("RSA");
        // 2. 初始化为加密模式,并传入公钥	
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        // 3. 用公钥加密数据(先转成字节数组)
        byte[] bytes = cipher.doFinal(data.getBytes());
        // 4. 把加密后的字节转成 Base64 字符串,方便存储和传输
        return Base64.getEncoder().encodeToString(bytes);
    }

    // ====== 解密(私钥) ======
    public static String decrypt(String data, PrivateKey privateKey) throws Exception {
        // 1. 创建一个 Cipher(密码器),指定算法为 RSA
        Cipher cipher = Cipher.getInstance("RSA");
        // 2. 初始化为解密模式,并传入私钥
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        // 3. 先把 Base64 字符串解码成字节数组
        byte[] bytes = Base64.getDecoder().decode(data);
        // 4. 用私钥解密字节数组,得到原始数据
        return new String(cipher.doFinal(bytes));
    }

    // ====== 签名(私钥) ======
    // SHA256withRSA:表示先用 SHA-256 做哈希,再用 RSA 私钥对哈希结果签名
    //    public static String sign(String data, PrivateKey privateKey) throws Exception {
    //        // 1. 创建 Signature 对象,指定算法为 SHA256withRSA
    //        Signature signature = Signature.getInstance("SHA256withRSA");
    //        // 2. 初始化为"签名模式",使用私钥
    //        signature.initSign(privateKey);
    //        // 3. 把要签名的数据输入进去(update 就是"喂数据"),可以多个update多次添加,这样就支持大文件了
    //        signature.update(data.getBytes());
    //        // 4. 生成签名字节数组
    //        byte[] signBytes = signature.sign();
    //        // 5. 转成 Base64 字符串,方便传输
    //        return Base64.getEncoder().encodeToString(signBytes);
    //    }

    // ====== 验签(公钥) ======
    //    public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
    //        // 1. 创建 Signature 对象,指定算法为 SHA256withRSA
    //        Signature signature = Signature.getInstance("SHA256withRSA");
    //        // 2. 初始化为"验证模式",使用公钥
    //        signature.initVerify(publicKey);
    //        // 3. 把原始数据输入进去
    //        signature.update(data.getBytes());
    //        // 4. 用公钥验证签名是否匹配
    //        return signature.verify(Base64.getDecoder().decode(sign));
    //    }

    public static SecretKey generateAESKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(128); // 128位足够
        return keyGen.generateKey();
    }

    // ====== AES 加密(CBC + PKCS5Padding) ======
    public static String aesEncrypt(String data, SecretKey key, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        byte[] encrypted = cipher.doFinal(data.getBytes());
        // 拼接 IV + 密文,一起传输
        byte[] result = new byte[iv.length + encrypted.length];
        System.arraycopy(iv, 0, result, 0, iv.length);
        System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
        return Base64.getEncoder().encodeToString(result);
    }

    // ====== AES 解密(CBC + PKCS5Padding) ======
    public static String aesDecrypt(String data, SecretKey key) throws Exception {
        byte[] decoded = Base64.getDecoder().decode(data);
        // 拆分出 IV 和密文
        byte[] iv = new byte[16];
        byte[] ciphertext = new byte[decoded.length - 16];
        System.arraycopy(decoded, 0, iv, 0, 16);
        System.arraycopy(decoded, 16, ciphertext, 0, ciphertext.length);

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
        byte[] decrypted = cipher.doFinal(ciphertext);
        return new String(decrypted);
    }

    // ====== 生成随机 IV ======
    public static byte[] generateIV() {
        byte[] iv = new byte[16]; // AES block size = 16 bytes
        new SecureRandom().nextBytes(iv);
        return iv;
    }

    // ====== 构建待签名字符串 ======
    public static String buildSignContent(SecureRequest request) {
        Map<String, String> map = new TreeMap<>();
        map.put("appId", request.getAppId());
        map.put("timestamp", request.getTimestamp());
        map.put("nonce", request.getNonce());
        map.put("data", request.getData());
        map.put("encryptKey", request.getEncryptKey());

        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
        }
        return sb.toString();
    }

    // ====== 签名 ======
    public static String signRequest(SecureRequest request, PrivateKey privateKey) throws Exception {
        // 构建待签名字符串
        String content = buildSignContent(request);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes());
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    // ====== 验签 ======
    public static boolean verifyRequest(SecureRequest request, PublicKey publicKey) throws Exception {
        // 构建待签名字符串
        String content = buildSignContent(request);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(content.getBytes());
        return signature.verify(Base64.getDecoder().decode(request.getSign()));
    }

}

PS:

加解密,使用了Cipher.getInstance("RSA"),这个其实是默认RSA/ECB/PKCS1Padding,确实存在一定的安全漏洞,以后改为Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"),具体修改以前的encryptdecrypt方法即可

java 复制代码
// 加密
public static String encrypt(String data, PublicKey publicKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    byte[] bytes = cipher.doFinal(data.getBytes());
    return Base64.getEncoder().encodeToString(bytes);
}
// 解密
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] bytes = Base64.getDecoder().decode(data);
    return new String(cipher.doFinal(bytes));
}
// 如果需要详细设置,则:
//OAEPParameterSpec oaepParams = new OAEPParameterSpec(
//        "SHA-256",
//        "MGF1",
//        MGF1ParameterSpec.SHA256,
//        PSource.PSpecified.DEFAULT
//);
//cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);

PS:使用SHA-256时,最大明文约为 190 字节

4-1-7.最终版
java 复制代码
package com.taiji;

import lombok.Data;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;

public class RSADemo {

    @Data
    public static class SecureRequest {

        /**
         * 应用ID(标识调用方)
         */
        private String appId;

        /**
         * 时间戳(防重放)
         */
        private String timestamp;

        /**
         * 随机串(防重放)
         */
        private String nonce;

        /**
         * 业务数据(AES加密后的Base64)
         */
        private String data;

        /**
         * AES密钥(用对方公钥RSA加密)
         */
        private String encryptKey;

        /**
         * 签名(对指定字段签名)
         */
        private String sign;
    }


    public static void main(String[] args) throws Exception {

        // ====== 1. 模拟 A 和 B 各自生成密钥对 ======
        KeyPair A = generateKeyPair();
        KeyPair B = generateKeyPair();
        // A的公私钥
        PublicKey A_pub = A.getPublic();
        PrivateKey A_pri = A.getPrivate();
        // B的公私钥
        PublicKey B_pub = B.getPublic();
        PrivateKey B_pri = B.getPrivate();

        // ====== 2. A -> B:发送数据(加密 + 签名) ======
        String original = "hello,我是A";
        // 生成 AES key
        SecretKey aesKey = generateAESKey();
        // 生成扰动因子IV
        byte[] iv = generateIV();
        // AES加密数据(IV 会拼接在密文前面)
        String encryptedData = aesEncrypt(original, aesKey, iv);
        // 用 B公钥 加密 AES key
        String encryptedKey = encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), B_pub);
        // 构建请求
        SecureRequest request = new SecureRequest();
        request.setAppId("appA");
        request.setTimestamp(String.valueOf(System.currentTimeMillis()));
        request.setNonce(String.valueOf(new SecureRandom().nextInt()));
        request.setData(encryptedData);
        request.setEncryptKey(encryptedKey);
        // 签名
        String signature = signRequest(request, A_pri);
        request.setSign(signature);

        System.out.println("加密数据: " + encryptedData);
        System.out.println("加密AES key: " + encryptedKey);
        System.out.println("签名: " + signature);

        // ====== 3. B 接收数据 ======
        // 用 B私钥 解密 AES key
        String aesKeyStr = decrypt(encryptedKey, B_pri);
        byte[] keyBytes = Base64.getDecoder().decode(aesKeyStr);
        SecretKey originAesKey = new SecretKeySpec(keyBytes, "AES");
        // 用 AES 解密数据
        String decrypted = aesDecrypt(encryptedData, originAesKey);
        // 验签
        boolean verify = verifyRequest(request, A_pub);

        System.out.println("解密后: " + decrypted);
        System.out.println("验签结果: " + verify);

    }

    // ====== 生成密钥对 ======
    public static KeyPair generateKeyPair() throws Exception {
        // 1. KeyPairGenerator秘钥生成器,使用RSA算法
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        // 2. 初始化生成器,设置密钥长度为 2048 位
        generator.initialize(2048);
        // 3. 生成密钥对(包含公钥和私钥)
        return generator.generateKeyPair();
    }

    public static String encrypt(String data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] bytes = cipher.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(bytes);
    }

    // ====== 解密(私钥) ======
    public static String decrypt(String data, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] bytes = Base64.getDecoder().decode(data);
        return new String(cipher.doFinal(bytes));
    }

    public static SecretKey generateAESKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(128); // 128位足够
        return keyGen.generateKey();
    }

    // ====== AES 加密(CBC + PKCS5Padding) ======
    public static String aesEncrypt(String data, SecretKey key, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        byte[] encrypted = cipher.doFinal(data.getBytes());
        // 拼接 IV + 密文,一起传输
        byte[] result = new byte[iv.length + encrypted.length];
        System.arraycopy(iv, 0, result, 0, iv.length);
        System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
        return Base64.getEncoder().encodeToString(result);
    }

    // ====== AES 解密(CBC + PKCS5Padding) ======
    public static String aesDecrypt(String data, SecretKey key) throws Exception {
        byte[] decoded = Base64.getDecoder().decode(data);
        // 拆分出 IV 和密文
        byte[] iv = new byte[16];
        byte[] ciphertext = new byte[decoded.length - 16];
        System.arraycopy(decoded, 0, iv, 0, 16);
        System.arraycopy(decoded, 16, ciphertext, 0, ciphertext.length);

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
        byte[] decrypted = cipher.doFinal(ciphertext);
        return new String(decrypted);
    }

    // ====== 生成随机 IV ======
    public static byte[] generateIV() {
        byte[] iv = new byte[16]; // AES block size = 16 bytes
        new SecureRandom().nextBytes(iv);
        return iv;
    }

    // ====== 构建待签名字符串 ======
    public static String buildSignContent(SecureRequest request) {
        Map<String, String> map = new TreeMap<>();
        map.put("appId", request.getAppId());
        map.put("timestamp", request.getTimestamp());
        map.put("nonce", request.getNonce());
        map.put("data", request.getData());
        map.put("encryptKey", request.getEncryptKey());

        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
        }
        return sb.toString();
    }

    // ====== 签名 ======
    public static String signRequest(SecureRequest request, PrivateKey privateKey) throws Exception {
        // 构建待签名字符串
        String content = buildSignContent(request);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes());
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    // ====== 验签 ======
    public static boolean verifyRequest(SecureRequest request, PublicKey publicKey) throws Exception {
        // 构建待签名字符串
        String content = buildSignContent(request);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(content.getBytes());
        return signature.verify(Base64.getDecoder().decode(request.getSign()));
    }

}

4-2.GCM混合加密

之前加解密用的AES/CBC/PKCS5Padding,其实已经不太推荐,安全性存在问题,CBC可能存在篡改问题,目前主流是AES/GCM/NoPadding

CBC与GCM的区别:

特性 CBC (Cipher Block Chaining) GCM (Galois/Counter Mode)
安全性 只保证机密性,需要额外签名来保证完整性 同时保证机密性 + 完整性(内置认证标签)
IV 要求 必须随机且唯一,重复会导致安全问题 必须唯一(通常用计数器或随机数),重复也会导致严重问题
并行性能 不支持并行加解密(每块依赖前一块) 支持并行加解密,速度更快
认证数据 需要额外签名机制(如 HMAC 或 RSA 签名) 可以直接把元数据(如 appId、timestamp)作为 AAD,自动校验
主流性 过去常用,但现在逐渐被淘汰 已成为主流推荐,TLS 1.3、JWT、现代 API 都用 GCM

代码实现:

java 复制代码
import lombok.Data;

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.util.Base64;
import java.util.Map;
import java.util.TreeMap;

public class RSADemo {

    @Data
    public static class SecureRequest {

        /**
         * 应用ID(标识调用方)
         */
        private String appId;

        /**
         * 时间戳(防重放)
         */
        private String timestamp;

        /**
         * 随机串(防重放)
         */
        private String nonce;

        /**
         * 业务数据(AES加密后的Base64)
         */
        private String data;

        /**
         * AES密钥(用对方公钥RSA加密)
         */
        private String encryptKey;

        /**
         * 签名(对指定字段签名)
         */
        private String sign;
    }


    public static void main(String[] args) throws Exception {

        // ====== 1. 模拟 A 和 B 各自生成密钥对 ======
        KeyPair A = generateKeyPair();
        KeyPair B = generateKeyPair();
        // A的公私钥
        PublicKey A_pub = A.getPublic();
        PrivateKey A_pri = A.getPrivate();
        // B的公私钥
        PublicKey B_pub = B.getPublic();
        PrivateKey B_pri = B.getPrivate();

        // ====== 2. A -> B:发送数据(加密 + 签名) ======
        String original = "hello,我是A";
        // 生成 AES key
        SecretKey aesKey = generateAESKey();
        // 生成GSM扰动因子IV
        byte[] iv = generateGcmIV();
        // AES加密数据(IV 会拼接在密文前面)
        String encryptedData = aesGcmEncrypt(original, aesKey, iv);
        // 用 B公钥 加密 AES key
        String encryptedKey = encrypt(Base64.getEncoder().encodeToString(aesKey.getEncoded()), B_pub);
        // 构建请求
        SecureRequest request = new SecureRequest();
        request.setAppId("appA");
        request.setTimestamp(String.valueOf(System.currentTimeMillis()));
        request.setNonce(String.valueOf(new SecureRandom().nextInt()));
        request.setData(encryptedData);
        request.setEncryptKey(encryptedKey);
        // 签名
        String signature = signRequest(request, A_pri);
        request.setSign(signature);

        System.out.println("加密数据: " + encryptedData);
        System.out.println("加密AES key: " + encryptedKey);
        System.out.println("签名: " + signature);

        // ====== 3. B 接收数据 ======
        // 用 B私钥 解密 AES key
        String aesKeyStr = decrypt(encryptedKey, B_pri);
        byte[] keyBytes = Base64.getDecoder().decode(aesKeyStr);
        SecretKey originAesKey = new SecretKeySpec(keyBytes, "AES");
        // 用 AES 解密数据
        String decrypted = aesGcmDecrypt(encryptedData, originAesKey);
        // 验签
        boolean verify = verifyRequest(request, A_pub);

        System.out.println("解密后: " + decrypted);
        System.out.println("验签结果: " + verify);

    }

    // ====== 生成密钥对 ======
    public static KeyPair generateKeyPair() throws Exception {
        // 1. KeyPairGenerator秘钥生成器,使用RSA算法
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        // 2. 初始化生成器,设置密钥长度为 2048 位
        generator.initialize(2048);
        // 3. 生成密钥对(包含公钥和私钥)
        return generator.generateKeyPair();
    }

    public static String encrypt(String data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] bytes = cipher.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(bytes);
    }

    // ====== 解密(私钥) ======
    public static String decrypt(String data, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] bytes = Base64.getDecoder().decode(data);
        return new String(cipher.doFinal(bytes));
    }

    public static SecretKey generateAESKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(128); // 128位足够
        return keyGen.generateKey();
    }

    // ====== 构建待签名字符串 ======
    public static String buildSignContent(SecureRequest request) {
        Map<String, String> map = new TreeMap<>();
        map.put("appId", request.getAppId());
        map.put("timestamp", request.getTimestamp());
        map.put("nonce", request.getNonce());
        map.put("data", request.getData());
        map.put("encryptKey", request.getEncryptKey());

        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
        }
        return sb.toString();
    }

    // ====== 签名 ======
    public static String signRequest(SecureRequest request, PrivateKey privateKey) throws Exception {
        // 构建待签名字符串
        String content = buildSignContent(request);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes());
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    // ====== 验签 ======
    public static boolean verifyRequest(SecureRequest request, PublicKey publicKey) throws Exception {
        // 构建待签名字符串
        String content = buildSignContent(request);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(content.getBytes());
        return signature.verify(Base64.getDecoder().decode(request.getSign()));
    }

    public static String aesGcmEncrypt(String data, SecretKey key, byte[] iv) throws Exception {
        // 使用 AES 算法,模式是 GCM(Galois/Counter Mode),不需要额外的填充(NoPadding)
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        // 这里的 128 表示认证标签(Authentication Tag)的长度是 128 位(16 字节)
        GCMParameterSpec spec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);
        // 执行加密
        byte[] encrypted = cipher.doFinal(data.getBytes());

        // 拼接格式为: [iv] + [密文]
        byte[] result = new byte[iv.length + encrypted.length];
        System.arraycopy(iv, 0, result, 0, iv.length);
        System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
            
        return Base64.getEncoder().encodeToString(result);
    }

    public static String aesGcmDecrypt(String data, SecretKey key) throws Exception {
        // 把传入的 Base64 字符串还原成字节数组
        byte[] decoded = Base64.getDecoder().decode(data);
        // GCM IV 12字节
        byte[] iv = new byte[12];
        // 前 12 字节是 IV,后面剩下的就是密文
        byte[] ciphertext = new byte[decoded.length - 12];
        System.arraycopy(decoded, 0, iv, 0, 12);
        System.arraycopy(decoded, 12, ciphertext, 0, ciphertext.length);
        // 用同样的密钥和 IV 初始化解密器
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec spec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.DECRYPT_MODE, key, spec);
        // 执行解密
        byte[] decrypted = cipher.doFinal(ciphertext);
        return new String(decrypted);
    }

    public static byte[] generateGcmIV() {
        // GCM 模式推荐使用 12 字节的 IV,因为这是标准且性能最佳
        byte[] iv = new byte[12];
        new SecureRandom().nextBytes(iv);
        return iv;
    }

}

5-生产级最佳实践

在实际系统对接中,一套完整的安全方案通常包括以下几个方面:

5-1.数据加密(机密性)

使用 AES-GCM 进行数据加密(推荐)

原因:

  • 同时提供机密性 + 完整性(内置认证标签)
  • 支持并行计算,性能更好

注意:

  • IV 必须唯一(推荐 12 字节随机数)

5-2.密钥交换

使用 RSA-OAEP(SHA-256) 加密 AES 密钥

原因:

  • 相比 PKCS1Padding,更安全(抗选择密文攻击)

注意:

  • RSA 只用于加密"短数据"(如 AES Key),不要直接加密业务数据

5-3.签名机制(身份认证 + 防篡改)

使用 SHA256withRSA 进行签名

签名内容建议包含:

  • appId
  • timestamp
  • nonce
  • ciphertext
  • encryptKey

作用:

  • 验证请求来源(是谁发的)
  • 防止数据被篡改
  • 防止接口参数被替换攻击

5-4.防重放攻击

必须包含:

  • timestamp(时间戳)
  • nonce(随机数)

服务端需要:

  • 校验时间窗口(如 ±5分钟)
  • nonce 做去重(可结合 Redis)

5-5.参数规范(签名一致性)

推荐使用 排序签名(Canonicalization)

  • 按 key 字典序排序
  • 拼接为:key=value&key=value

原因:

  • 避免 JSON 顺序导致签名不一致
  • 提高跨语言兼容性

5-6.传输安全(必须)

全部接口必须走 HTTPS

原因:

  • 防止中间人攻击(MITM)
  • TLS 已提供一层基础加密
相关推荐
XDOC2 小时前
使用docx4j将Word文档转换为PDF
java
Chockmans2 小时前
春秋云境CVE-2020-25483
web安全·网络安全·春秋云境·cve-2020-25483
heimeiyingwang2 小时前
【架构实战】混沌工程:让系统更健壮的实践
开发语言·架构·php
cch89182 小时前
PHP vs C#:语言对比与实战选型
开发语言·c#·php
KevinCyao2 小时前
Ruby短信营销接口示例代码:Ruby开发环境下营销短信API接口的集成与Demo演示
开发语言·前端·ruby
葳_人生_蕤2 小时前
hot100——双指针法专题
java·前端·数据库
让学习成为一种生活方式2 小时前
取消“为 LAN 使用代理服务器”--随笔023
开发语言·php
天桥下的卖艺者2 小时前
R语言使用TrialEmulation包快速进行数据模拟RCT研究(真实世界研究)
开发语言·r语言·模拟rct
无风听海2 小时前
NET10之C# Primary Constructor 深度指南
开发语言·c#·.net10