cryptographybouncycastlefipsaes-gcm

AES Encryption | InvalidCipherTextException: MAC check in GCM failed, while decrypting the cipher text at c# side


I am stuck while decrypting a cipher text (at C# side) which was encrypted using Microsoft's BCRYPT.h at c++ side. I am getting exception saying Org.BouncyCastle.Crypto.InvalidCipherTextException: MAC check in GCM failed.

As far as I know it is related to MACTag mismatch or any other parameter mismatch. I have verified that I am using the same values but still it is not working.

Below is the flow I am using:

  1. Encrypting the text using BCryptEncrypt function of BCRYPT.h at c++ side.
  2. using the same key, iv and aad at c# side.
  3. At c# end I am using bc-fips-1.0.2.dll to decrypt.

Please find the below Code which I am using:

At C++ Side ( I have formatted the lines in bold which are used as data at c# side)

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);**
}

At C# Side:

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;
        }

Please guide me to resolve this error.


Solution

  • There are minor mistakes in your implementation which may be due to lack of documentation reading and non-standard way of Algorithm Outputs. I have debugged your code snippet and find below loose points which I think are common while working on cross platform and multiple algorithm implementation:

    1. Microsoft's BCrypt implementation of AES-GCM doesn't provide tag and encrypted data all together (unlike in BouncyCastle it is appended with the encrpted output itself.)

    pbTag parameter will have the Authentication Tag. (authTag in your case)

    pbOutput parameter will have the encrypted data. (encrypted in your case)

    1. For AeadUsage.AAD_FIRST in BC lib, you should have your decryptiong input in below format: (auth Tag must be in the end of encrypted data)

    Encrypted Data bytes + Auth Tag Bytes

    You can do something like below at c# end (or similar thing can be done at c++ side itself).

    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.As indicated by @dave_thompson_85 in comment use pbResult ( ulWritten in your case) parameter i.e. number of bytes encrypted to take the encrypted bytes.

    `base64_encode_new(encrypted, ulWritten);`
    
    1. using BYTE origData2[] = "test"; will add an extra \0 in the byte error which will cause the Tag Mismatch. Update it to something else(like below) and make sure about this at any other place.

      std::string origData2("test");

    2. You are confused with AAD and AuthTag, please learn about those. if you have not specified the AAD during encryption you should not specify it during decryption as well. Remove the below two lines from AesDecrypt method.

    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);
    

    Additionally If you wish to use your own AAD, then during encryption you have to assign the pbAuthData, cbAuthData parameters in BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure. (and same AAD should be added at BC side.)