【问题标题】:How to upgrade from RijndaelManaged to AES?如何从 RijndaelManaged 升级到 AES?
【发布时间】:2023-02-11 01:54:52
【问题描述】:

我的代码中有一个加密/解密数据的工作解决方案(如下),但是当我将项目升级到 DOTNET6 时,RijndaelManaged 变得过时了:

警告 SYSLIB0022 'RijndaelManaged' 已过时:'Rijndael 和 RijndaelManaged 类型已过时。请改用 Aes。

SYSLIB0023 'RNGCryptoServiceProvider' 已过时: 'RNGCryptoServiceProvider 已过时。要生成随机数,请改用 RandomNumberGenerator 静态方法之一。'

现在我想按照说明将其更改为 Aes/RandomNumberGenerator,但希望保持输出与原样相同。不幸的是,我不熟悉 crypt/decrypt。

有人可以帮助我重写当前块以改为使用 Aes - 或者至少帮助我如何更改它并保持公共方法以相同的方式工作?

我拥有的全部代码(按原样工作)

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);
    }
}

【问题讨论】:

  • AES 是 Rijndael——它们是同一回事。出于某种原因,.NET 有几个类做几乎完全相同的事情。您应该能够将对 RijndaelManaged 的​​所有引用更改为 AES,而无需任何功能更改
  • 我可以确认@canton7 所说的内容。我们最近不得不将我们的代码转换为使用符合 FIPS 的加密,我们所要做的就是从 RijndaelManaged 切换到 Aes
  • 代码实现了AES,但是严格来说AES和Rijndael是不一样的。 AES 是 Rijndael 的子集。 Rijndael(这不是标准的 btw)以 32 位步长使用 128 位和 256 位之间的块和密钥大小,而 AES 使用 128 位的固定块大小和 128、192 和 256 位的密钥大小。
  • 感谢您提供信息 - 已完成,我可以确认升级很容易。

标签: c# cryptography aes rijndaelmanaged


【解决方案1】:

据我所知,你应该可以更换

using RijndaelManaged AES = new();

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

请注意,您可能希望更改变量名称以避免命名冲突或混淆。

AES 和 Rijndael 之间的唯一区别应该是 Rijndael 允许更多的块大小/密钥大小。但是您似乎正在使用 256 位密钥和 128 位块,这应该是 AES 允许的。

【讨论】:

  • Aes.Create("AesManaged") 警告我,当我想使用创建的对象时,该值可以为空。我应该做 null 检查,还是 [CanBeNull] 只适用于一般的 Aes.Create(string) 方法并且使用正确的字符串参数(“AesManaged”)不需要 null 检查?
  • @matronator 我会参考documentation。不幸的是,尚不清楚何时返回 null。我的猜测是当参数无效时。
  • 是的,文档没有提到它,这就是我来问的原因。如果参数不是文档中指定的参数之一,那也是我的第一个想法。可悲的是,Rider 没有意识到我使用了正确的论点,所以它无论如何都会显示警告,而我的 OCD 真的因为这些黄色下划线的警告而发疯了......:D
  • @matronator 然后我建议禁用警告。并可能添加一个 Debug.Assert,这样如果将来行为发生变化,您就有机会收到通知。
  • 您可以执行 var AES = Aes.Create("AesManaged")!; 来停止空警告
猜你喜欢
  • 2014-08-22
  • 2012-11-09
  • 1970-01-01
  • 2021-10-15
  • 1970-01-01
  • 1970-01-01
  • 2013-01-24
  • 2013-04-19
  • 2023-03-15
相关资源
最近更新 更多