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

AES解密报错:GCM模式MAC校验失败(C#与C++跨语言适配)

解决C#端解密BCRYPT加密GCM密文时的MAC校验失败问题

问题背景

使用C#的bc-fips-1.0.2解密C++通过BCRYPT.h加密的AES-GCM密文时,抛出Org.BouncyCastle.Crypto.InvalidCipherTextException,提示MAC check in GCM failed。已确认密钥、IV、AAD一致,但问题仍存在。

核心问题排查与修复

1. C++端加密数据长度与输出缓冲区不匹配

C++代码中,加密的是origData2(字符串"test",包含末尾\0,sizeof(origData2)为5),但输出缓冲区encrypted按AES块长(16字节)分配,且最终把整个16字节缓冲区Base64编码传给C#。这导致C#端解密时处理了额外的随机填充字节,破坏MAC校验。

  • 修复:加密后仅对BCryptEncrypt返回的实际写入长度(ulWritten)的字节进行Base64编码,而非整个缓冲区长度。

2. C++端未使用AAD但C#端传入了AAD

C++代码的BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO未设置pbAuthData和cbAuthData,说明加密时未使用AAD,但C#端却传入了aadAuthData。GCM模式中AAD参与MAC计算,两端必须完全一致(要么都传,要么都不传)。

  • 修复:要么C++端加密时添加AAD参数,要么C#端移除AAD写入逻辑。

3. MAC标签传递与长度一致性

C端使用authTagLengths.dwMinLength(通常为96位/12字节),C#端设置macBitSize = 96是正确的,但需确保C端完整传递标签数据,且C#端使用的标签长度与加密端完全匹配。

4. BCRYPT缓冲区未初始化的干扰

C++代码中encrypted通过HeapAlloc分配但未初始化,未被加密覆盖的字节是随机值,这些随机值被一起Base64传给C#,导致解密数据错误。必须仅传递实际加密后的字节。

修正后的关键代码示例

C++端修正后的加密逻辑片段

std::vector<BYTE> encrypted(origDataSize2);
ULONG ulWritten = 0;

{
    BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
    BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
    authInfo.pbNonce = (PUCHAR)&origNonce[0];
    authInfo.cbNonce = origNonce.size();
    authInfo.pbTag = &authTag[0];
    authInfo.cbTag = authTag.size();
    // 若需添加AAD,取消以下两行注释并传入对应数据
    // authInfo.pbAuthData = (PUCHAR)aadBuffer;
    // authInfo.cbAuthData = aadBufferLength;

    status = BCryptEncrypt
    (
        hKeyAes,
        origData2, origDataSize2,
        &authInfo,
        0, 0,
        &encrypted[0], encrypted.size(),
        &ulWritten, 0
    );
}
// 仅编码实际加密的字节数
std::string encryptedStringUsedAtCSharpSide = base64_encode_new(&encrypted[0], ulWritten);

C#端修正后的解密逻辑片段(移除不必要的AAD)

private static byte[] AesDecrypt()
{
    byte[] aesKey = Convert.FromBase64String("KSO+hOFs1q5SkEnx8bvp6w==");
    byte[] iv = Convert.FromBase64String("s6bbPIcMPpkkXg0c");
    byte[] encryptedData = Convert.FromBase64String("q/KR75wAAAAAAAAAAAAAAA==");
    int macBitSize = 96; // 与C++端标签长度一致
    byte[] decryptedMessage = null;

    try
    {
        CryptoServicesRegistrar.SetApprovedOnlyMode(true);
        var key = new FipsAes.Key(aesKey);
        IAeadCipherService cipherService = CryptoServicesRegistrar.CreateService(key);
        var algorithmDetails = FipsAes.Gcm.WithIV(iv).WithMacSize(macBitSize);
        var aeadDecryptorBuilder = cipherService.CreateAeadDecryptorBuilder(algorithmDetails);

        // 注意:若C++端分开传递密文和标签,需将标签附加到密文末尾后再传入
        var decryptor = aeadDecryptorBuilder.BuildAeadCipher(AeadUsage.NO_AAD, new MemoryInputStream(encryptedData));

        // 移除AAD写入(C++端未使用AAD)
        // decryptor.AadStream.Write(aadAuthData, 0, aadAuthData.Length);

        using (var stream = decryptor.Stream)
        {
            decryptedMessage = Streams.ReadAll(stream);
        }
    }
    catch
    {
        throw;
    }
    return decryptedMessage;
}

额外注意事项

  • 密文与标签的拼接规则:BCRYPT默认生成独立的密文和标签,而BouncyCastle的GCM实现通常期望密文在前、标签在后拼接成一个字节数组。若C++端分开传递两者,C#端需手动拼接后再解密。
  • Base64编码一致性:确保两端使用相同的Base64标准(如是否保留填充符=、是否使用URL安全编码)。
  • 明文与密文长度匹配:AES-GCM是流加密,加密后长度等于明文长度,无需额外填充,禁止传递多余的缓冲区字节。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 12:54:55