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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 11:15:39