The problem: I have implemented some functionality that does decryption at specific events, but SOMETIMES it throws unexpected and unexplained exceptions.
Flow: A master key/iv is fetched then data (from a PostgreSQL database is fetched) and decoded from base64 and then decrypted and then decompressed and decoded again. So summarizing it goes like this:
- Fetch Key/Iv
- Decode From Base64
- Decrypt with AES-256 CBC
- Decompress from Gzip (At this point it fails sometimes)
- Decode again to UTF-8
This is the code
public async Task<string> PrivateDecrypt(long userId, string data){ var aesData = await this.GetOrAddPrivateEncryptionData(userId); var decrypted = await data.FromBase64().AESDecryptAsync(aesData.Key, aesData.IV); var decompressed = decrypted.DecompressFromGzip(); return decompressed.ToUTF8String();}
public static byte[] FromBase64(this string base64) => Convert.FromBase64String(base64);
public static async Task<byte[]> AESDecryptAsync(this byte[] cipher, string key, string iv){ if (cipher == null || cipher.Length <= 0) throw new ArgumentNullException(nameof(cipher), "incorrect data"); if (key == null || key.Length != 32) throw new ArgumentNullException(nameof(key), "incorrect key"); if (iv == null || iv.Length != 16) throw new ArgumentNullException(nameof(iv), "incorrect iv"); using var aesAlg = Aes.Create(); aesAlg.Mode = CipherMode.CBC; aesAlg.KeySize = 256; aesAlg.Key = key.ToUTF8Bytes(); aesAlg.IV = iv.ToUTF8Bytes(); aesAlg.Padding = PaddingMode.PKCS7; using var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); return await PerformCryptographyAsync(cipher, decryptor).ConfigureAwait(false);}
private static async Task<byte[]> PerformCryptographyAsync(byte[] data, ICryptoTransform cryptoTransform){ using var memoryStream = new MemoryStream(); using var cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write); await cryptoStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); await cryptoStream.FlushFinalBlockAsync().ConfigureAwait(false); return memoryStream.ToArray();}
This is part of the exception stack trace I receive
System.IO.InvalidDataException: The archive entry was compressed using an unsupported compression method. at System.IO.Compression.Inflater.Inflate(FlushCode flushCode) at System.IO.Compression.Inflater.ReadInflateOutput(Byte* bufPtr, Int32 length, FlushCode flushCode, Int32& bytesRead) at System.IO.Compression.Inflater.InflateVerified(Byte* bufPtr, Int32 length) at System.IO.Compression.DeflateStream.CopyToStream.CopyFromSourceToDestination() at Trading.Tools.Bytes.CompressionExtensions.DecompressFromGzip(Byte[] data) in /src/Trading.Tools/Bytes/CompressionExtensions.cs:line 25 at Trading.Web.Commons.Encryption.LocalEncryptionService.PrivateDecrypt(Int64 userId, String data) in /src/Trading.Web.Commons/Encryption/LocalEncryptionService.cs:line 99
As you can see from the stacktrace the problem lies in the decompression layer AFTER the decryption.
Hypothesis:
- This code I can say for sure that it works
- This exception that I encounter happens quite rarely
- Data in database is correct, otherwise it would never work
My thoughts:
- I believe that due to probabilistic nature of the problem then this can be a race condition. There are 2 possibilities I see:
- Either the database doesn't return me the full string and it escapes some character by mistake, but then how does decryption work ?
- Either decryption returns weird results sometimes which leads me to think that even if there are separate instances there is still a shared state somewhere ?
Additional Info:
- I'm using EFCore 8.0.2 for fetching from database
- I'm using .NET Core 8.0.0
- I'm using PostgreSQL 16.1-bullseye