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:
This is the code
public async Task<byte[]> PrivateDecrypt(long userId, byte[] data)
{
var key = await this.GetOrAddPrivateEncryptionKey(userId);
var keyBytes = key.ToUTF8Bytes();
var (cipher, ivBytes) = data.ExtractIVPadding();
var decrypted = await cipher
.AESDecryptAndDecompressAsync(keyBytes, ivBytes)
.ConfigureAwait(false);
return decrypted;
}
public static byte[] FromBase64(this string base64) => Convert.FromBase64String(base64);
public static async Task<byte[]> AESDecryptAndDecompressAsync(this byte[] cipher, byte[] key, byte[] 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;
aesAlg.IV = iv;
aesAlg.Padding = PaddingMode.PKCS7;
using var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (var decompressedStream = new MemoryStream())
{
using (var encryptedStream = new MemoryStream(cipher))
{
using (var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read))
{
using (var gzipStream = new GZipStream(cryptoStream, CompressionMode.Decompress))
await gzipStream.CopyToAsync(decompressedStream).ConfigureAwait(false);
return decompressedStream.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.WriteAsyncCore(ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
at System.Security.Cryptography.CryptoStream.CopyToAsyncInternal(Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
at System.IO.Compression.DeflateStream.CopyToStream.CopyFromSourceToDestinationAsync()
at Trading.Tools.Bytes.EncryptionExtensions.AESDecryptAndDecompressAsync(Byte[] cipher, Byte[] key, Byte[] iv) in /src/Trading.Tools/Bytes/EncryptionExtensions.cs:line 207
at Trading.Web.Commons.Encryption.LocalEncryptionService.PrivateDecrypt(Int64 userId, Byte[] data) in /src/Trading.Web.Commons/Encryption/LocalEncryptionService.cs:line 107
As you can see from the stacktrace the problem lies in the decompression layer AFTER the decryption.
Hypothesis:
My thoughts:
Additional Info:
UPDATE 1: I've updated the code to @Charlieface suggestions, but unfortunately I still face the same issues
UPDATE 2: I replaced all the encryption logic with BouncyCastle Nuget and got rid of the issue. I did not have this problem prior to 8.0.3 so I only can assume it was because of the latest patch
I replaced all the encryption logic with BouncyCastle Nuget and got rid of the issue. I did not have this problem prior to 8.0.3 so I only can assume it was because of the latest patch
Below is the only change I've made.
public static async Task<byte[]> AESDecryptAndDecompressAsync(this byte[] cipher, byte[] key, byte[] 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");
var cipherEngine = new PaddedBufferedBlockCipher(new CbcBlockCipher(new AesEngine()), new Pkcs7Padding());
cipherEngine.Init(false, new ParametersWithIV(new KeyParameter(key), iv));
var decryptedBytes = new byte[cipherEngine.GetOutputSize(cipher.Length)];
var len = cipherEngine.ProcessBytes(cipher, 0, cipher.Length, decryptedBytes, 0);
len += cipherEngine.DoFinal(decryptedBytes, len);
var result = new byte[len];
Array.Copy(decryptedBytes, result, len);
using var decompressedStream = new MemoryStream();
using (var ms = new MemoryStream(result))
{
using (var gzipStream = new GZipStream(ms, CompressionMode.Decompress))
await gzipStream.CopyToAsync(decompressedStream).ConfigureAwait(false);
}
return decompressedStream.ToArray();
}