Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RijndaelManaged returns empty result

The Problem
I want to decrypt encrypted data with RijndaelManaged but the result is always empty (either "" or an byte array with the length of the data full of zeros).

All parameters, salt and data are all correct, CryptoHelper.CreateRijndaelManagedAES gets called the exact same way in the encrypt method (which produces an good output).

The only thing left I could think of is that I use the streams wrong, but I can't figure out why ...

Code

public static RijndaelManaged CreateRijndaelManagedAES(byte[] passwordHash, byte[] salt)
{
    RijndaelManaged aes = new RijndaelManaged
    {
        KeySize = 256,
        BlockSize = 128,
        Padding = PaddingMode.PKCS7,
        Mode = CipherMode.CBC
    };

    // Derive a key of the full Argon2 string (contains also meta data)
    using Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(passwordHash, salt, 10);
    aes.Key = key.GetBytes(aes.KeySize / 8);
    aes.IV = key.GetBytes(aes.BlockSize / 8);

    return aes;
}

public static async Task<string> EncryptDataAsync(string plainData, byte[] passwordHash, int saltSize)
{
    return await Task.Run(async () =>
    {
        // Generate a random salt
        byte[] salt = CryptoHelper.GenerateRandomSalt(saltSize);

        // Write the salt unencrypted
        using MemoryStream memoryStream = new MemoryStream();
        await memoryStream.WriteAsync(salt);

        // Encrypt the data and write the result to the stream
        using RijndaelManaged aes = CryptoHelper.CreateRijndaelManagedAES(passwordHash, salt);
        using CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
        using StreamWriter streamWriter = new StreamWriter(cryptoStream);
        await streamWriter.WriteAsync(plainData);
        cryptoStream.FlushFinalBlock();

        return ENCRYPTED_MAGIC + Convert.ToBase64String(memoryStream.ToArray());
    });
}

public static async Task<string> DecryptDataAsync(string encryptedData, byte[] passwordHash, int saltSize)
{
    if (!HasValidMagicBytes(encryptedData))
    {
        throw new ArgumentException("The given data isn't encrypted");
    }

    return await Task.Run(async () =>
    {
        byte[] saltAndData = Convert.FromBase64String(encryptedData.Substring(ENCRYPTED_MAGIC.Length));
        byte[] salt = saltAndData.Take(saltSize).ToArray();
        byte[] data = saltAndData.TakeLast(saltAndData.Length - saltSize).ToArray();

        // Decrypt the data and return the result
        using MemoryStream memoryStream = new MemoryStream(data);
        using RijndaelManaged aes = CryptoHelper.CreateRijndaelManagedAES(passwordHash, salt);
        using CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Read);
        using StreamReader streamReader = new StreamReader(cryptoStream);
        return await streamReader.ReadToEndAsync();
    });
}

Security Notice
Don't use Rfc2898DeriveBytes with only 10 iterations (as I do) if you pass a password to it. I derive a key from the password beforehand due to performance reasons (Blazor WASM) and pass the result to the CreateRijndaelManagedAES function.
If you want to use Rfc2898DeriveBytes with a password you should use at least 50000 iterations (as of 2020).

Explantation
The iterations parameter is basically a cost parameter. The higher the iterations count the harder it gets for an attacker to brute force the derived password (as he would need more computing power/time). NIST has made a publication in 2017 which it states that you should use as many iterations as your environment can handle but at least 10000. I can't find the source anymore but I remember to have read that you currently should use at least 50000 (due to future security).

like image 492
91378246 Avatar asked Nov 16 '25 18:11

91378246


1 Answers

The issue is in the EncryptDataAsync method, i.e. the encryption (in DecryptDataAsync, i.e. the decryption, the bug only becomes evident). This is because the StreamWriter must first be flushed before memoryStream.ToArray() is called. This call must be executed before:

...
cryptoStream.FlushFinalBlock(); 
...

that is:

...
streamWriter.Flush();
cryptoStream.FlushFinalBlock(); 
...

or alternatively

...
streamWriter.Close();
...

which flushes/closes both streams, see also StreamWriter.Flush() and StreamWriter.Close().

like image 180
Topaco Avatar answered Nov 18 '25 07:11

Topaco



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!