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

C实现AES/GCM加密后Java解密报Tag Mismatch问题求助

AES/GCM跨语言解密Tag Mismatch问题排查

问题描述

使用C(基于OpenSSL)和Java分别实现AES/GCM/NoPadding算法,C端加密后将Base64编码的密钥、IV、密文传入Java端解密时,出现**标签不匹配(Tag Mismatch)**错误。

C端代码

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/rand.h>

#define AES_KEY_SIZE 32 // 256-bit key
#define AES_IV_SIZE 12   // 96-bit IV
#define AES_TAG_SIZE 16  // 128-bit tag

const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

char *base64_encode(const unsigned char *input, size_t input_length) {
    size_t output_length = 4 * ((input_length + 2) / 3); // Calculate the output buffer length
    char *output = (char *) malloc(output_length + 1); // Add space for null terminator
    if (!output) {
        fprintf(stderr, "Memory allocation failed\n");
        return NULL;
    }

    size_t i, j = 0;
    for (i = 0; i < input_length; i += 3) {
        unsigned char a = i < input_length ? input[i] : 0;
        unsigned char b = i + 1 < input_length ? input[i + 1] : 0;
        unsigned char c = i + 2 < input_length ? input[i + 2] : 0;

        output[j++] = base64_chars[a >> 2];
        output[j++] = base64_chars[((a & 0x03) << 4) | ((b & 0xF0) >> 4)];
        output[j++] = i + 1 < input_length ? base64_chars[((b & 0x0F) << 2) | ((c & 0xC0) >> 6)] : '=';
        output[j++] = i + 2 < input_length ? base64_chars[c & 0x3F] : '=';
    }

    output[j] = '\0'; // Add null terminator
    return output;
}

int aes_256_gcm_encrypt(const unsigned char *plaintext, int plaintext_len,
                        const unsigned char *key, const unsigned char *iv,
                        unsigned char *ciphertext, unsigned char *tag) {
    EVP_CIPHER_CTX *ctx;
    int len, ciphertext_len;

    // Create and initialize the context
    if (!(ctx = EVP_CIPHER_CTX_new()))
        return -1;

    // Initialize the encryption operation with AES-256-GCM
    if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
        return -1;

    // Set IV length
    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, AES_IV_SIZE, NULL))
        return -1;

    // Initialize key and IV
    if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv))
        return -1;

    // Perform encryption
    if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
        return -1;
    ciphertext_len = len;

    // Finalize encryption
    if (!EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
        return -1;
    ciphertext_len += len;

    // Get tag
    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, AES_TAG_SIZE, tag))
        return -1;

    // Clean up
    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}

int aes_256_gcm_decrypt(const unsigned char *ciphertext, int ciphertext_len,
                        const unsigned char *tag, const unsigned char *key,
                        const unsigned char *iv, unsigned char *plaintext) {
    EVP_CIPHER_CTX *ctx;
    int len, plaintext_len, ret;

    // Create and initialize the context
    if (!(ctx = EVP_CIPHER_CTX_new()))
        return -1;

    // Initialize the decryption operation with AES-256-GCM
    if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
        return -1;

    // Set IV length
    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, AES_IV_SIZE, NULL))
        return -1;

    // Initialize key and IV
    if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv))
        return -1;

    // Provide the message tag and its length
    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, AES_TAG_SIZE, (void *)tag))
        return -1;

    // Perform decryption
    if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
        return -1;
    plaintext_len = len;

    // Finalize decryption
    ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
    EVP_CIPHER_CTX_free(ctx);

    if (ret > 0) {
        plaintext_len += len;
        return plaintext_len;
    } else {
        // Verification failed
        return -1;
    }
}

int main() {
    unsigned char key[AES_KEY_SIZE]; // 256-bit key
    unsigned char iv[AES_IV_SIZE];   // 96-bit IV
    unsigned char tag[AES_TAG_SIZE]; // 128-bit tag

    unsigned char plaintext[] = "Hello, AES-256-GCM!";
    int plaintext_len = strlen((char *)plaintext);

    unsigned char ciphertext[plaintext_len];
    unsigned char decryptedtext[plaintext_len];

    // Generate random key and IV
    RAND_bytes(key, sizeof(key));
    RAND_bytes(iv, sizeof(iv));

    printf("Encoded data key: %s\n", base64_encode((unsigned char *)key, 32));
    printf("Encoded data iv: %s\n", base64_encode((unsigned char *)iv, 12));

    // Encrypt
    int ciphertext_len = aes_256_gcm_encrypt(plaintext, plaintext_len, key, iv, ciphertext, tag);
    if (ciphertext_len == -1) {
        printf("Encryption failed\n");
        return -1;
    }

    printf("Encoded data ciphertext: %s\n", base64_encode(ciphertext, ciphertext_len));
    // 新增:输出标签的Base64编码
    printf("Encoded data tag: %s\n", base64_encode(tag, AES_TAG_SIZE));

    // Decrypt
    int decryptedtext_len = aes_256_gcm_decrypt(ciphertext, ciphertext_len, tag, key, iv, decryptedtext);
    if (decryptedtext_len == -1) {
        printf("Decryption failed\n");
        return -1;
    }

    // Output
    printf("Original Text: %s\n", plaintext);
    printf("Encrypted Text: ");
    for (int i = 0; i < ciphertext_len; ++i) {
        printf("%02x", ciphertext[i]);
    }
    printf("\n");
    printf("Decrypted Text: %s\n", decryptedtext);

    return 0;
}

Java端代码

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.util.encoders.Base64;

public class AESGCMExample {
    public static void main(String[] args) throws Exception {
        // 替换为C端输出的实际Base64编码值
        byte[] decryptedSessionKey = Base64.decode("pu2K/1t/+LHllgP0ZrXMo9Ziohsd4bZjX4BXbQecX6E=".getBytes());
        byte[] decryptedSessioniv = Base64.decode("Kx1oMw57qEejXB8C".getBytes());
        byte[] decryptedCipherText = Base64.decode("M1tqaz8/az8/QSo/Kz8/Pz8=".getBytes());
        // 新增:解码C端输出的标签Base64
        byte[] decryptedTag = Base64.decode("替换为C端输出的标签Base64".getBytes());

        SecretKey secretKey2 = new SecretKeySpec(decryptedSessionKey, "AES");

        // Decrypt
        byte[] decryptedText = decrypt(decryptedCipherText, secretKey2, decryptedSessioniv, decryptedTag);

        // Print results
        System.out.println("Decrypted Text: " + new String(decryptedText));
    }

    public static byte[] encrypt(byte[] plainText, SecretKey secretKey, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);
        return cipher.doFinal(plainText);
    }

    public static byte[] decrypt(byte[] cipherText, SecretKey secretKey, byte[] iv, byte[] tag) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
        // 新增:设置GCM标签
        cipher.setTag(tag);
        return cipher.doFinal(cipherText);
    }
}

问题排查与解决提示

核心问题1:密文Base64编码长度错误

C端原代码中,对密文进行Base64编码时使用了strlen((const char *)ciphertext)获取长度,但密文是二进制数据,可能包含\0字符,导致strlen提前截断,编码后的密文不完整。Java端解码后得到的密文长度不足,直接引发标签验证失败。

修复方式:使用加密函数返回的ciphertext_len作为Base64编码的输入长度,即把原代码中的:

printf("Encoded data ciphertext: %s\n", base64_encode((char *)ciphertext, strlen((const char *)ciphertext)));

改为:

printf("Encoded data ciphertext: %s\n", base64_encode(ciphertext, ciphertext_len));

核心问题2:GCM标签未传递

GCM模式的解密需要同时验证密文和标签,OpenSSL会单独生成标签,但原C代码只输出了密文的Base64,未输出标签;Java端也没有接收和设置标签,导致解密时无法完成验证,触发Tag Mismatch错误。

修复方式:

  1. 在C端主函数中新增标签的Base64输出:
    printf("Encoded data tag: %s\n", base64_encode(tag, AES_TAG_SIZE));
    
  2. 修改Java端的decrypt方法,添加标签参数,并在初始化Cipher后调用cipher.setTag(tag)设置标签,再执行解密操作。

可选优化:替换自定义Base64实现

C端自定义的Base64实现存在潜在风险(如边界处理、兼容性问题),建议使用OpenSSL自带的EVP_EncodeBlock函数替代,避免手动实现带来的错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:56:00