bcgit / bc-csharp

BouncyCastle.NET Cryptography Library (Mirror)
https://www.bouncycastle.org/csharp
MIT License
1.68k stars 559 forks source link

[BUG] GCM Decryption Fails with Invalid Ciphertext in AES-GCM (Tag Mismatch) #575

Closed NKCSS closed 6 days ago

NKCSS commented 1 week ago

Describe the Bug

AES-GCM decryption using BouncyCastle's GcmBlockCipher class results in a mac check in GCM failed error, while the same parameters (key, IV, ciphertext, and tag) successfully decrypt in .NET's built-in AesGcm class. The issue seems to stem from differences in how BouncyCastle and .NET handle AES-GCM tag verification and decryption, particularly around tag and ciphertext handling.

To Reproduce

Verify in Python:

import os
import sys
import binascii
from Crypto.Cipher import AES

key = binascii.a2b_base64("J9wM1F6PyyoeQke5kSl0hyzb1N0TpqSFOC5U7iYAob4=")
cookie_iv = binascii.a2b_base64("gKRNTE6rd4F/ijoi")
cookie_tag = binascii.a2b_base64("3K64moVuibc3OQaFgqLN0Q==")
encrypted_cookie = binascii.a2b_base64("qImdc8EmM7TGrWmCX/AV/Xia/fHDuRUF1meVzWv+Q9Z5sKdVB7g7NG62l7r/Td+44vU5I/7E3qA5")
cookie_cipher = AES.new(key, AES.MODE_GCM, nonce=cookie_iv)
decrypted_cookie = cookie_cipher.decrypt_and_verify(encrypted_cookie, cookie_tag)
dec = decrypted_cookie.decode('utf-8')
print("decrypted: %s" % dec)

Verify in C#

byte[] key = Convert.FromBase64String("J9wM1F6PyyoeQke5kSl0hyzb1N0TpqSFOC5U7iYAob4=");
byte[] iv = Convert.FromBase64String("gKRNTE6rd4F/ijoi"); 
byte[] tag = Convert.FromBase64String("3K64moVuibc3OQaFgqLN0Q==");
byte[] encrypted = Convert.FromBase64String("qImdc8EmM7TGrWmCX/AV/Xia/fHDuRUF1meVzWv+Q9Z5sKdVB7g7NG62l7r/Td+44vU5I/7E3qA5"); 
using (var aes = new AesGcm(key))
{
    var plaintextBytes = new byte[encrypted.Length];
    aes.Decrypt(iv, encrypted, tag, plaintextBytes);
    Console.WriteLine(Encoding.UTF8.GetString(plaintextBytes));
}

Failed Decryption in C# with BouncyCastle:

using System;
using System.Text;
using Org.BouncyCastle.Crypto.Cipher;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Engines;

public class AESGCMDecryptionExample
{
    public static string DecryptCookie()
    {
        byte[] key = Convert.FromBase64String("J9wM1F6PyyoeQke5kSl0hyzb1N0TpqSFOC5U7iYAob4=");
        byte[] iv = Convert.FromBase64String("gKRNTE6rd4F/ijoi"); 
        byte[] tag = Convert.FromBase64String("3K64moVuibc3OQaFgqLN0Q=="); 
        byte[] encrypted = Convert.FromBase64String("qImdc8EmM7TGrWmCX/AV/Xia/fHDuRUF1meVzWv+Q9Z5sKdVB7g7NG62l7r/Td+44vU5I/7E3qA5");
        GcmBlockCipher gcmCipher = new GcmBlockCipher(new AesEngine());
        AeadParameters parameters = new AeadParameters(new KeyParameter(key), 128, iv, tag); 
        gcmCipher.Init(false, parameters); 
        byte[] decryptedBytes = new byte[gcmCipher.GetOutputSize(encrypted.Length)];
        int len = gcmCipher.ProcessBytes(encrypted, 0, encrypted.Length, decryptedBytes, 0);
        try
        {
            gcmCipher.DoFinal(decryptedBytes, len);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Decryption failed: " + ex.Message);
            throw;
        }
        string decryptedData = Encoding.UTF8.GetString(decryptedBytes, 32, decryptedBytes.Length - 32);
        return decryptedData;
    }
    public static void Main()
    {
        try
        {
            string decryptedData = DecryptCookie();
            Console.WriteLine($"Decrypted: {decryptedData}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

Expected Behavior

Successful decryption with the tag verified.

Screenshots and Logs

Actual Output: mac check in GCM failed error.

Observations

The same parameters (key, IV, tag, and ciphertext) decrypt correctly using .NET's built-in AesGcm class. The Decrypt method of .NET's AesGcm automatically handles tag verification and decryption without issues. When using BouncyCastle, it appears that there's a mismatch or misalignment between how the tag and ciphertext are handled, resulting in the error.

Product Deployment

Please complete the following information:

Desktop

Please complete the following information:

peterdettman commented 1 week ago

This is incorrect usage of the BC API. Replace lines 6-9 of DecryptCookie with this:

AeadParameters parameters = new AeadParameters(new KeyParameter(key), 128, iv, null);
gcmCipher.Init(false, parameters);
byte[] decryptedBytes = new byte[gcmCipher.GetOutputSize(encrypted.Length + tag.Length)];
int len = gcmCipher.ProcessBytes(encrypted, 0, encrypted.Length, decryptedBytes, 0);
len += gcmCipher.ProcessBytes(tag, 0, tag.Length, decryptedBytes, len);

...and it now decrypts fine.

Note that the associatedText parameter of AeadParameters is not for the tag; it is a way to pass "additional authenticated data" (AAD) that AEAD modes support (another way is to use ProcessAadByte(s) calls). We pass null here to indicate there is no AAD.

The BC implementation instead expects the tag to be included at the end of the ciphertext. So we change the input to GetOutputSize and also pass the tag data using a second ProcessBytes call. Note that when encrypting with BC it will also include the tag at the end of the ciphertext automatically.