How to upgrade from RijndaelManaged to AES?
Asked Answered
P

1

20

I have a working solution for crypt/decrypt data in my code (below) but when I have upgraded the project to DOTNET6, RijndaelManaged becomes obsolete:

Warning SYSLIB0022 'RijndaelManaged' is obsolete: 'The Rijndael and RijndaelManaged types are obsolete. Use Aes instead.'

and

SYSLIB0023 'RNGCryptoServiceProvider' is obsolete: 'RNGCryptoServiceProvider is obsolete. To generate a random number, use one of the RandomNumberGenerator static methods instead.'

Now I want to change that to Aes/RandomNumberGenerator as stated but want to keep the output in the same way as is. Unfortunately, I am not familiar with crypt/decrypt.

Can somebody help me to rewrite the current block to work with Aes instead - or at least help me how to change that and keep the public methods works in the same way?

Whole Code I have (it works as is)

using System.Security.Cryptography;

namespace MyApp;

internal static class AES
{
    private static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
    {
        byte[] encryptedBytes;
        byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };

        using (MemoryStream ms = new())
        {
            using RijndaelManaged AES = new(); // This reports Warning  SYSLIB0022  'RijndaelManaged' is obsolete: 'The Rijndael and RijndaelManaged types are obsolete. Use Aes instead.
            AES.KeySize = 256;
            AES.BlockSize = 128;
            var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
            AES.Key = key.GetBytes(AES.KeySize / 8);
            AES.IV = key.GetBytes(AES.BlockSize / 8);
            AES.Mode = CipherMode.CBC;
            using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
            {
                cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
                cs.Close();
            }
            encryptedBytes = ms.ToArray();
        }
        return encryptedBytes;
    }

    private static byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
    {
        byte[] decryptedBytes = null;
        byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
        using (MemoryStream ms = new())
        {
            using RijndaelManaged AES = new(); // This reports Warning  SYSLIB0022  'RijndaelManaged' is obsolete: 'The Rijndael and RijndaelManaged types are obsolete. Use Aes instead.
            AES.KeySize = 256;
            AES.BlockSize = 128;
            var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
            AES.Key = key.GetBytes(AES.KeySize / 8);
            AES.IV = key.GetBytes(AES.BlockSize / 8);
            AES.Mode = CipherMode.CBC;
            using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
            {
                cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
                cs.Close();
            }
            decryptedBytes = ms.ToArray();
        }
        return decryptedBytes;
    }

    public static string EncryptText(string password, string salt = "MySecretSaltWhichIWantToKeepWorking")
    {
        byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(password);
        byte[] passwordBytes = Encoding.UTF8.GetBytes(salt);
        passwordBytes = SHA256.Create().ComputeHash(passwordBytes);
        byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, passwordBytes);
        string result = Convert.ToBase64String(bytesEncrypted);
        return result;
    }

    public static string DecryptText(string hash, string salt = "MySecretSaltWhichIWantToKeepWorking")
    {
        try
        {
            byte[] bytesToBeDecrypted = Convert.FromBase64String(hash);
            byte[] passwordBytes = Encoding.UTF8.GetBytes(salt);
            passwordBytes = SHA256.Create().ComputeHash(passwordBytes);
            byte[] bytesDecrypted = AES_Decrypt(bytesToBeDecrypted, passwordBytes);
            string result = Encoding.UTF8.GetString(bytesDecrypted);
            return result;
        }
        catch (Exception e)
        {
            return e.Message;
        }
    }

    private const int SALT_BYTE_SIZE = 24;
    private const int HASH_BYTE_SIZE = 24;
    private const int PBKDF2_ITERATIONS = 1000;
    private const int ITERATION_INDEX = 0;
    private const int SALT_INDEX = 1;
    private const int PBKDF2_INDEX = 2;

    public static string PBKDF2_CreateHash(string password)
    {
        RNGCryptoServiceProvider csprng = new(); // This reports SYSLIB0023 'RNGCryptoServiceProvider' is obsolete: 'RNGCryptoServiceProvider is obsolete. To generate a random number, use one of the RandomNumberGenerator static methods instead.'
        byte[] salt = new byte[SALT_BYTE_SIZE];
        csprng.GetBytes(salt);
        byte[] hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);
        return PBKDF2_ITERATIONS + ":" + Convert.ToBase64String(salt) + ":" + Convert.ToBase64String(hash);
    }

    public static bool PBKDF2_ValidatePassword(string password, string correctHash)
    {
        char[] delimiter = { ':' };
        string[] split = correctHash.Split(delimiter);
        int iterations = Int32.Parse(split[ITERATION_INDEX]);
        byte[] salt = Convert.FromBase64String(split[SALT_INDEX]);
        byte[] hash = Convert.FromBase64String(split[PBKDF2_INDEX]);
        byte[] testHash = PBKDF2(password, salt, iterations, hash.Length);
        return SlowEquals(hash, testHash);
    }

    private static bool SlowEquals(byte[] a, byte[] b)
    {
        uint diff = (uint)a.Length ^ (uint)b.Length;
        for (int i = 0; i < a.Length && i < b.Length; i++)
            diff |= (uint)(a[i] ^ b[i]);
        return diff == 0;
    }

    private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
    {
        Rfc2898DeriveBytes pbkdf2 = new(password, salt)
        {
            IterationCount = iterations
        };
        return pbkdf2.GetBytes(outputBytes);
    }
}
Pleader answered 3/12, 2021 at 9:31 Comment(4)
AES is Rijndael -- they're the same thing. .NET had several classes which did pretty much exactly the same thing, for some reason. You should just be able to change all references to RijndaelManaged to AES without any change in functionalityBullfrog
I can confirm what @Bullfrog says. We recently had to convert our code to use FIPS-compliant encryption, and all we had to do was switch from RijndaelManaged to AesDhiman
The code implements AES, but strictly speaking AES and Rijndael are not the same. AES is a subset of Rijndael. Rijndael (which is not the standard btw) uses block and key sizes between 128 bits and 256 bits in 32 bit steps, while AES uses a fixed block size of 128 bits and key sizes 128, 192, and 256 bits.Escapism
Thanks for the information - it is DONE and I can confirm that the upgrade was easy.Tamqrah
I
28

As far as I can tell, you should be able to replace

using RijndaelManaged AES = new();

with

using var AES = Aes.Create("AesManaged");

note that you might want to change your variable name to avoid naming conflicts or confusion.

The only difference between AES and Rijndael should be that Rijndael allows more blocksizes/keysizes. But you seem to be using 256 bit key and 128 bit blocks, and this should be allowed by AES.

Infield answered 3/12, 2021 at 10:6 Comment(9)
Aes.Create("AesManaged") is giving me warning that the value can be null though, when I want to use the created object. Should I do null checks, or is the [CanBeNull] only to the general Aes.Create(string) method and with the correct string parameter ("AesManaged") the null checks aren't necessary?Rib
@Rib I would refer to the documentation. Unfortunately it is not clear when a null would be returned. My guess would be for when the argument is invalid.Infield
Yeah the docs don't mention it, that's why I came to ask. That's what my first thought was as well, if the argument isn't one of the specified in the docs. Sadly Rider doesn't recognize that I used the correct argument, so it shows the warning regardless and my OCD is really going crazy with these yellow underlines for warnings... :DRib
@Rib Then I would suggest disabling the warning. And possibly add a Debug.Assert, so that if the behavior changes in the future you have a decent chance to be notified.Infield
You can do var AES = Aes.Create("AesManaged")!; to stop the null warningCharente
looks like need to use : Aes.Create();Finegrain
Having recently upgraded some very old .NET Framework code, I also needed to explicitly set the FeedbackSize property when switching from RijndaelManaged to Aes.Create(), i.e. "aes.FeedbackSize = 128" because the default was set to 8 bits on my platform.Liaoyang
@KevinD. Curious, the documentation for AesManaged notes about FeedbackSize: "Because this algorithm does not support feedback modes, using this property is discouraged." So what mode are you using?Infield
@JonasH, I'm using the CFB cipher mode. According to the .NET documentation, the FeedbackSize property may need to be set because the default can vary by algorithm: learn.microsoft.com/en-us/dotnet/api/…Liaoyang

© 2022 - 2024 — McMap. All rights reserved.