AES 加密 | InvalidCipherTextException:GCM 中的 MAC 检查失败,同时在 C# 端解密密文

问题描述 投票:0回答:1

我在解密密文(在 C# 端)时陷入困境,该密文是在 C++ 端使用 Microsoft 的 BCRYPT.h 加密的。我收到异常消息 Org.BouncyCastle.Crypto.InvalidCipherTextException: GCM 中的 MAC 检查失败。

据我所知,这与 MACTag 不匹配或任何其他参数不匹配有关。我已经验证我使用的是相同的值,但它仍然不起作用。

以下是我正在使用的流程:

  1. 在 C++ 端使用 BCRYPT.h 的 BCryptEncrypt 函数加密文本。
  2. 在 C# 端使用相同的密钥 iv 和 aad。
  3. 在c#端我使用bc-fips-1.0.2.dll来解密。

请找到我正在使用的以下代码:

在 C++ 端(我已将行格式化为粗体,这些行在 C# 端用作数据)

void Encrypt()
{
    BCRYPT_ALG_HANDLE hAlgorithmAes = NULL;
    BCRYPT_KEY_HANDLE hKeyAes = NULL;
    ULONG cbKeyObjectLength = 0;
    ULONG ulWritten = 0;
    UCHAR* pbKeyObjectAes = 0;
    UCHAR  iv[12] = { 0 };
    UCHAR tag[16] = { 0 };

    NTSTATUS status = BCryptOpenAlgorithmProvider(
        &hAlgorithmAes,
        BCRYPT_AES_ALGORITHM,
        NULL, 0
    );

    if (!BCRYPT_SUCCESS(status)) {
        printf("Failed to get algorithm provider for AES..status : %08x\n", status);
        //goto cleanup;
    }

    status = BCryptSetProperty(
        hAlgorithmAes,
        BCRYPT_CHAINING_MODE,
        (BYTE*)BCRYPT_CHAIN_MODE_GCM,
        sizeof(BCRYPT_CHAIN_MODE_GCM),
        0
    );

    if (!BCRYPT_SUCCESS(status)) {
        printf("Failed to set property for AES..status : %08x\n", status);
        //goto cleanup;
    }

    BCRYPT_AUTH_TAG_LENGTHS_STRUCT authTagLengths;
    status = BCryptGetProperty(hAlgorithmAes, BCRYPT_AUTH_TAG_LENGTH, (BYTE*)&authTagLengths, sizeof(authTagLengths), &ulWritten, 0);
    if (!BCRYPT_SUCCESS(status))
    {

    }

    DWORD blockLength = 0;
    status = BCryptGetProperty(hAlgorithmAes, BCRYPT_BLOCK_LENGTH, (BYTE*)&blockLength, sizeof(blockLength), &ulWritten, 0);
    if (!BCRYPT_SUCCESS(status))
    {

    }

    const std::vector<BYTE> keyAes = MakeRandomBytes(blockLength);
    
    **std::string keyUsedAtCSharpSide = base64_encode_new(&keyAes[0], keyAes.size());**

    status = BCryptGenerateSymmetricKey(hAlgorithmAes, &hKeyAes, 0, 0, (PUCHAR)&keyAes[0], keyAes.size(), 0);
    if (!BCRYPT_SUCCESS(status))
    {

    }
    const size_t GCM_NONCE_SIZE = 12;
    const std::vector<BYTE> origNonce = MakeRandomBytes(GCM_NONCE_SIZE);
    **std::string ivNonceUsedAtCSharpSide = base64_encode_new(&origNonce[0], origNonce.size());**
    BYTE origData2[] = "test";
    DWORD origDataSize2 = sizeof(origData2);
    const std::vector<BYTE> origData = MakePatternBytes(18);

    // Encrypt data as a whole
    PBYTE encrypted = (PBYTE)HeapAlloc(GetProcessHeap(), 0, blockLength);

    std::vector<BYTE> authTag(authTagLengths.dwMinLength);
    {
        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();
        
        status = BCryptEncrypt
        (
            hKeyAes,
            // &encrypted[0], encrypted.size(),
            origData2, origDataSize2,
            &authInfo,
            0, 0,
            &encrypted[0], blockLength,
            &ulWritten, 0
        );
    }
    **std::string tagIdUsedAtCSharpSide = base64_encode_new(&authTag[0], authTag.size());**
    **std::string encryptedStringUsedAtCSharpSide = base64_encode_new(encrypted, blockLength);**
}

在 C# 端:

private static byte[] AesDecrypt()
        {
//Below Data is taken from the c++ side and hardcoded here
              byte[]  aesKey = Convert.FromBase64String("KSO+hOFs1q5SkEnx8bvp6w==");
              byte[]  iv = Convert.FromBase64String("s6bbPIcMPpkkXg0c");
              byte[]  encryptedData = Convert.FromBase64String("q/KR75wAAAAAAAAAAAAAAA==");
            int macBitSize = 96;
            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);
                IAeadCipherBuilder<IParameters<FipsAlgorithm>> aeadDecryptorBuilder = cipherService.CreateAeadDecryptorBuilder(algorithmDetails);

                var decryptor = aeadDecryptorBuilder.BuildAeadCipher(AeadUsage.AAD_FIRST, new MemoryInputStream(encryptedData));
                
//Below Data is taken from the c++ side and hardcoded here
                byte[] aadAuthData =Convert.FromBase64String("HX0BeEQNYbrpjA23");// Strings.ToByteArray( This message was sent 29th Feb at 11.00am - does not repeat");
                decryptor.AadStream.Write(aadAuthData, 0, aadAuthData.Length);
                /**/
                using (var stream = decryptor.Stream)
                {
                    decryptedMessage = Streams.ReadAll(stream);
                }
            }
            catch
            {
                throw;
            }
            return decryptedMessage;
        }

请指导我解决此错误。

cryptography bouncycastle fips aes-gcm
1个回答
0
投票

您的实现中存在一些小错误,这可能是由于缺乏文档阅读和算法输出的非标准方式造成的。我已经调试了您的代码片段,并找到了以下松散点,我认为这些松散点在跨平台和多种算法实现时很常见:

  1. Microsoft 的 AES-GCM BCrypt 实现并不同时提供标签和加密数据(与 BouncyCastle 不同,它附加了加密输出本身。)

pbTag参数将具有身份验证标签。 (authTag在你的情况下)

pbOutput参数将包含加密数据。 (在您的情况下加密

  1. 对于 BC lib 中的 AeadUsage.AAD_FIRST,您的解密输入应采用以下格式:(auth Tag 必须位于加密数据的末尾)

加密数据字节+身份验证标签字节

您可以在 C# 端执行类似下面的操作(或者可以在 C++ 端本身执行类似的操作)。

var tag = Convert.FromBase64String("YadCQ6V4c3nOUdniqlzymw ==");
encryptedData = Convert.FromBase64String("q/KR7/U=");
byte[] data = new byte[tag.Length + encryptedData.Length];
encryptedData.CopyTo(data, 0);
tag.CopyTo(data, encryptedData.Length);

3.正如@dave_thompson_85在评论中所指出的,使用pbResult(在您的情况下是ulWritten)参数,即加密字节数以获取加密字节。

`base64_encode_new(encrypted, ulWritten);`
  1. 使用

    BYTE origData2[] = "test";
    会在字节错误中添加额外的内容,这将导致标签不匹配。将其更新为其他内容(如下所示)并在任何其他地方确保这一点。

    std::string origData2("test");

  2. 您对AAD和AuthTag感到困惑,请了解一下。如果您在加密期间未指定 AAD,则也不应在解密期间指定它。从 AesDecrypt 方法中删除以下两行。

byte[] aadAuthData =Convert.FromBase64String("HX0BeEQNYbrpjA23");// Strings.ToByteArray("This message was sent 29th Feb at 11.00am - does not repeat");
decryptor.AadStream.Write(aadAuthData, 0, aadAuthData.Length);

另外 如果您希望使用自己的 AAD,则在加密期间,您必须在 BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO 结构中分配 pbAuthData、cbAuthData 参数。 (BC侧也应添加相同的AAD。)

© www.soinside.com 2019 - 2024. All rights reserved.