You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AES GCM模式下BouncyCastle二次解密失败问题求助

AES-GCM加解密对接C系统的IV问题解决

问题背景

使用BouncyCastle实现AES-GCM加解密逻辑,对接C语言开发的第三方系统。首次加解密基于第三方提供的会话密钥与IV可正常执行,但二次加解密失败,推测因重复使用同一IV导致。需解决新IV的生成逻辑及传递方案(必须传递IV,否则对方无法完成解密)。

当前Java加解密代码

public byte[] encrypt(byte[] plainBytes) {
    byte[] encryptedBytes = new byte[0];
    try {
        GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
        AEADParameters parameters =
                new AEADParameters(new KeyParameter(key), MacBitSize, iv);

        cipher.init(true, parameters);

        encryptedBytes = new byte[cipher.getOutputSize(plainBytes.length)];
        int retLen = cipher.processBytes
                (plainBytes, 0, plainBytes.length, encryptedBytes, 0);
        cipher.doFinal(encryptedBytes, retLen);
    } catch (IllegalArgumentException |
             IllegalStateException | DataLengthException |
             InvalidCipherTextException ex) {
        System.out.println(ex.getMessage());
    }
    System.arraycopy(aad, 0, encryptedBytes, plainBytes.length, 16);
    byte[] final_encr = new byte[plainBytes.length];
    System.arraycopy(encryptedBytes, 0, final_encr, 0, plainBytes.length);
    return final_encr;
}

public byte[] decrypt(byte[] encryptedBytes) {
    byte[] plainBytes = new byte[0];
    try {
        GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
        AEADParameters parameters =
                new AEADParameters(new KeyParameter(key), MacBitSize, iv);

        cipher.init(false, parameters);
        cipher.processAADBytes(aad, 0, aad.length);
        plainBytes = new byte[encryptedBytes.length];
        int retLen = cipher.processBytes
                (encryptedBytes, 0, encryptedBytes.length, plainBytes, 0);
        retLen += cipher.processBytes(aad, 0, aad.length, plainBytes, retLen);
        retLen += cipher.doFinal(plainBytes, retLen);
    } catch (IllegalArgumentException | IllegalStateException |
             DataLengthException | InvalidCipherTextException ex) {
        System.out.println(ex.getMessage());
    }
    return plainBytes;
}

已尝试的无效方案

  • 修改IV末尾四位并递增
  • 采用C JNI库实现
  • 使用kalium、JCE等其他库

第三方C语言加解密实现(基于OpenSSL)

第三方文档中的加解密方法

对称加解密方法说明:

加密

初始化
void encrypt_EVP_aes_256_cbc_init(EVP_CIPHER_CTX ** ctx, unsigned char * key,
    unsigned char * iv) {
    if (!( * ctx = EVP_CIPHER_CTX_new()))
        handleErrors();

    if (1 != EVP_EncryptInit_ex( * ctx, EVP_aes_256_gcm(), NULL, key, iv))
        handleErrors();
}
加密逻辑
void encrypt(EVP_CIPHER_CTX * ctx, unsigned char * plaintext, int plaintext_len,
    unsigned char * ciphertext, int * ciphertext_len) {
    int len;

    if (1 != EVP_EncryptUpdate(ctx, ciphertext, & len, plaintext, plaintext_len))
        handleErrors();
    * ciphertext_len = len;
}

解密

初始化
void decrypt_EVP_aes_256_cbc_init(EVP_CIPHER_CTX ** ctx, unsigned char * key,
    unsigned char * iv) {
    if (!( * ctx = EVP_CIPHER_CTX_new()))
        handleErrors();

    if (1 != EVP_DecryptInit_ex( * ctx, EVP_aes_256_gcm(), NULL, key, iv))
        handleErrors();
}
解密逻辑
int decrypt(EVP_CIPHER_CTX * ctx, unsigned char * ciphertext, int ciphertext_len,
    unsigned char * plaintext, int * plaintext_len) {
    int len;

    if (1 != EVP_DecryptUpdate(ctx, plaintext, & len, ciphertext, ciphertext_len))
        handleErrors();
    * plaintext_len = len;
}

说明

  • 加粗部分为OpenSSL库函数
  • plaintext为实际消息缓冲区
  • ciphertext为加密消息缓冲区

解决方案

1. 合规的IV生成逻辑

AES-GCM要求同一密钥下的IV必须完全唯一,绝对不能重复。推荐两种标准生成方式:

  • 随机IV:生成12字节(96位)的随机字节数组,这是NIST推荐的GCM最优IV长度,安全性最高。代码实现:
    SecureRandom secureRandom = new SecureRandom();
    byte[] newIv = new byte[12]; // 12字节=96位,GCM标准推荐长度
    secureRandom.nextBytes(newIv);
    
  • 计数器IV:若需要有序IV,可基于初始IV维护一个全局计数器,每次加密后递增(需确保密钥有效期内计数器不会溢出)。注意计数器需采用大端模式,长度建议为12字节。

2. IV的传递方案

解密方必须获取对应IV才能解密,常用两种传递方式:

  • 前置IV到密文中:加密完成后,将IV拼接在密文最前端,最终传输内容格式为:[IV(12字节)] + [密文+GCM标签]。接收方先读取前12字节作为IV,剩余部分作为密文+标签进行解密。修改后的加密和解密代码示例:
    public byte[] encrypt(byte[] plainBytes) {
        try {
            // 生成新IV
            SecureRandom secureRandom = new SecureRandom();
            byte[] newIv = new byte[12];
            secureRandom.nextBytes(newIv);
    
            GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
            // 传入AAD参数(若第三方需要AAD需提前约定)
            AEADParameters parameters = new AEADParameters(new KeyParameter(key), 128, newIv, aad);
    
            cipher.init(true, parameters);
            int outputSize = cipher.getOutputSize(plainBytes.length);
            byte[] cipherWithTag = new byte[outputSize];
            int retLen = cipher.processBytes(plainBytes, 0, plainBytes.length, cipherWithTag, 0);
            cipher.doFinal(cipherWithTag, retLen);
    
            // 拼接IV + 密文+标签
            byte[] finalEncrypted = new byte[newIv.length + cipherWithTag.length];
            System.arraycopy(newIv, 0, finalEncrypted, 0, newIv.length);
            System.arraycopy(cipherWithTag, 0, finalEncrypted, newIv.length, cipherWithTag.length);
            return finalEncrypted;
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
            return new byte[0];
        }
    }
    
    解密代码:
    public byte[] decrypt(byte[] encryptedBytes) {
        if (encryptedBytes.length < 12) {
            return new byte[0];
        }
        // 拆分IV
        byte[] receivedIv = new byte[12];
        System.arraycopy(encryptedBytes, 0, receivedIv, 0, 12);
        byte[] cipherWithTag = new byte[encryptedBytes.length - 12];
        System.arraycopy(encryptedBytes, 12, cipherWithTag, 0, cipherWithTag.length);
    
        try {
            GCMBlockCipher cipher = new GCMBlockCipher(new AESFastEngine());
            AEADParameters parameters = new AEADParameters(new KeyParameter(key), 128, receivedIv, aad);
    
            cipher.init(false, parameters);
            // 处理AAD(需和第三方保持一致)
            cipher.processAADBytes(aad, 0, aad.length);
    
            byte[] plainBytes = new byte[cipher.getOutputSize(cipherWithTag.length)];
            int retLen = cipher.processBytes(cipherWithTag, 0, cipherWithTag.length, plainBytes, 0);
            cipher.doFinal(plainBytes, retLen);
            return plainBytes;
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
            return new byte[0];
        }
    }
    
  • 独立字段传递:如果对接协议中有单独的IV字段,可将生成的IV存入该字段,与密文一同发送给第三方。

3. 额外注意事项

  • 标签长度:GCM标签建议使用128位(16字节),与OpenSSL默认值一致,确保双方兼容。
  • AAD处理:原代码中错误地将AAD拼接在密文中后又截断,正确做法是通过processAADBytes在加密前传入,若第三方需使用AAD,需提前约定传递方式。
  • 密文完整性:原加密代码截断了GCM标签(仅返回明文长度的内容),导致解密无法验证完整性,这也是二次解密失败的可能原因,必须保留完整的密文+标签。

内容的提问来源于stack exchange,提问作者Suraj Putta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 21:07:01