C# AES/GCM加密后Java解密报Tag Mismatch问题求助
解决C#(Bouncy Castle)与Java AES/GCM跨语言解密Tag不匹配问题
核心问题总结
你的代码存在多处跨语言不兼容的细节错误,直接导致了AEADBadTagException:
- 密钥长度不符合需求(要求32字节,但C#端实际生成16字节密钥)
- GCM Nonce使用错误且未随加密数据传递给Java端
- 密文、Tag、Nonce的拆分逻辑完全混乱
- 字符串编码不一致(C#用ASCII转明文,易导致非ASCII字符丢失)
逐个问题修正
1. 统一密钥生成逻辑(满足32字节要求)
你明确要求使用32字节AES密钥,但C#代码中对msgId哈希后只取了前16字节,生成的是128位密钥,不符合需求。需要调整为取前32字节:
修正后的C#密钥生成段:
using (var hasher = SHA512.Create()) { byte[] digestSeed = hasher.ComputeHash(secretKey); hashKey = new byte[32]; // 改为32字节,对应AES-256 Array.Copy(digestSeed, hashKey, hashKey.Length); }
Java端的密钥生成必须和C#完全对齐,如果Java端的密钥是从msgId生成,需执行相同逻辑:
// Java端生成对应密钥的示例代码 byte[] secretKeyBytes = msgId.getBytes(StandardCharsets.UTF_8); MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); byte[] digestSeed = sha512.digest(secretKeyBytes); byte[] hashKey = Arrays.copyOf(digestSeed, 32); SecretKey key = new SecretKeySpec(hashKey, "AES");
2. 修复GCM Nonce的使用与传递
GCM模式要求Nonce必须全局唯一(同一密钥下不能重复用同一个Nonce加密不同数据),推荐使用12字节随机Nonce。同时C#端必须把Nonce和密文+Tag一起发送给Java端,因为解密依赖Nonce。
修正后的C#加密函数:
public static string Encrypt(string plainText, string msgId) { const int GcmTagSize = 16; const int GcmNonceSize = 12; // GCM标准推荐的Nonce长度 // 生成32字节AES密钥 byte[] secretKey = Encoding.UTF8.GetBytes(msgId); byte[] hashKey; using (var hasher = SHA512.Create()) { byte[] digestSeed = hasher.ComputeHash(secretKey); hashKey = new byte[32]; Array.Copy(digestSeed, hashKey, hashKey.Length); } // 生成随机12字节Nonce byte[] nonce = new byte[GcmNonceSize]; using (var rng = new RNGCryptoServiceProvider()) { rng.GetBytes(nonce); } // 初始化GCM加密器 var keyParameter = new KeyParameter(hashKey); var aeadParams = new AeadParameters(keyParameter, GcmTagSize * 8, nonce); var cipher = CipherUtilities.GetCipher("AES/GCM/NoPadding"); cipher.Init(true, aeadParams); // 加密明文(Bouncy Castle的DoFinal返回密文+Tag的拼接数据) byte[] plainTextData = Encoding.UTF8.GetBytes(plainText); // 改用UTF8和Java统一编码 byte[] cipherTextWithTag = cipher.DoFinal(plainTextData); // 拼接Nonce + 密文+Tag,转Base64后发送 byte[] finalData = new byte[nonce.Length + cipherTextWithTag.Length]; Buffer.BlockCopy(nonce, 0, finalData, 0, nonce.Length); Buffer.BlockCopy(cipherTextWithTag, 0, finalData, nonce.Length, cipherTextWithTag.Length); return Convert.ToBase64String(finalData); }
3. 修正Java端的解密逻辑
现在C#端发送的数据结构是Nonce(12字节) + 密文 + Tag(16字节),Java端需要正确拆分并解密:
修正后的Java解密函数:
private static byte[] decrypt(byte[] message, SecretKey key) throws Exception { Cipher cipher = Cipher.getInstance("AES/GCM/NOPadding"); final int GCM_NONCE_SIZE = 12; final int GCM_TAG_SIZE = 16; // 拆分Nonce和密文+Tag byte[] nonce = Arrays.copyOfRange(message, 0, GCM_NONCE_SIZE); byte[] cipherTextWithTag = Arrays.copyOfRange(message, GCM_NONCE_SIZE, message.length); // 初始化GCM参数 GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_SIZE * 8, nonce); cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec); // Java的Cipher.doFinal会自动识别并验证Tag,直接传入密文+Tag即可 return cipher.doFinal(cipherTextWithTag); }
4. 统一字符串编码
C#端原代码用Encoding.ASCII.GetBytes(plainText),若明文包含非ASCII字符会丢失信息,改为Encoding.UTF8与Java端统一;Java解密后用new String(decryptedValue, StandardCharsets.UTF_8)转换为字符串。
验证要点
- 两端密钥生成逻辑必须完全一致:对msgId做UTF8编码→SHA512哈希→取前32字节
- Nonce必须随机生成且随加密数据传递,同一密钥下绝对不能重复使用
- 加密后的数据结构统一为:Nonce在前,密文+Tag在后
- 全程使用UTF8编码处理字符串,避免字符乱码或丢失
内容的提问来源于stack exchange,提问作者Morpheus.47
相关产品推荐
相关产品推荐

