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错误。
修复方式:
- 在C端主函数中新增标签的Base64输出:
printf("Encoded data tag: %s\n", base64_encode(tag, AES_TAG_SIZE)); - 修改Java端的
decrypt方法,添加标签参数,并在初始化Cipher后调用cipher.setTag(tag)设置标签,再执行解密操作。
可选优化:替换自定义Base64实现
C端自定义的Base64实现存在潜在风险(如边界处理、兼容性问题),建议使用OpenSSL自带的EVP_EncodeBlock函数替代,避免手动实现带来的错误。
内容的提问来源于stack exchange,提问作者Prateek
相关产品推荐
相关产品推荐

