C# - PBDKF2,空值

C# - PBDKF2, null values

我在 C# 中实现 AES,对于 IV 和密钥,我通过 Rfc2898DeriveBytes Class 使用 PBDKF2。当我 运行 它时,输入文本是加密的,当我查看 "key" 变量中的数据时,有一个错误说 "Hash = 'key.m_hmacsha1.Hash' threw an exception of type 'System.NullReferenceException'",以及其他 null values/0s其他方法。我怀疑我没有在我的代码中正确实现它,而且我在尝试诊断它时遇到了麻烦。我希望从在 C# 中实现 AES 的人那里获得见解。另外,如果用户输入了错误的密钥,我将如何在异常中捕获它?代码贴在下面。

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace AES
{
class Program
{
    public static byte[] salt;
    public static byte[] saltBytes;

    static void Main(string[] args)
    {
        var encrypted = secure.EncryptText("abc", "123");

        Console.WriteLine(encrypted);

        Console.WriteLine(secure.DecryptText(encrypted, "123"));
    }

    public string EncryptText(string input, string password)
    {
        // Get the bytes of the string
        byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(input);
        byte[] passwordBytes = Encoding.UTF8.GetBytes(password);

        // Hash the password with SHA256
        passwordBytes = SHA256.Create().ComputeHash(passwordBytes);

        byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, passwordBytes);

        string result = Convert.ToBase64String(bytesEncrypted);

        return result;
    }

    public string DecryptText(string input, string password)
    {
        // Get the bytes of the string
        byte[] bytesToBeDecrypted = Convert.FromBase64String(input);
        byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
        passwordBytes = SHA256.Create().ComputeHash(passwordBytes);

        byte[] bytesDecrypted = AES_Decrypt(bytesToBeDecrypted, passwordBytes);

        string result = Encoding.UTF8.GetString(bytesDecrypted);

        return result;
    }

    public byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
    {
        byte[] encryptedBytes = null;

        // Set your salt here, change it to meet your flavor:
        // The salt bytes must be at least 8 bytes.

        new RNGCryptoServiceProvider().GetBytes(salt = new byte[32]);
        saltBytes = salt;

        using (MemoryStream ms = new MemoryStream())
        {
            using (RijndaelManaged AES = new RijndaelManaged())
            {

                AES.KeySize = 256;
                AES.BlockSize = 128;

                var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
                AES.Key = key.GetBytes(AES.KeySize / 8);
                var iv = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
                AES.IV = iv.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;
    }

    public byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
    {

        byte[] decryptedBytes = null;

        // Set your salt here, change it to meet your flavor:
        // The salt bytes must be at least 8 bytes.

        using (MemoryStream ms = new MemoryStream())
        {
            using (RijndaelManaged AES = new RijndaelManaged())
            {
                AES.KeySize = 256;
                AES.BlockSize = 128;

                var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
                AES.Key = key.GetBytes(AES.KeySize / 8);
                var iv = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
                AES.IV = iv.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;
    }

}
}

when I view the data in the "key" variable, there's an error that says "Hash = 'key.m_hmacsha1.Hash' threw an exception of type 'System.NullReferenceException'", along with other null values/0s in other methods.

您正在检查不属于您的 class 的私有字段。这并不总能带来明智的结果。相反,您应该只依赖 public API.

if a user enters in the wrong key, how would I catch this in an exception?

您将获得指示 "the padding is invalid and cannot be removed" 的异常的可能性 (~15/16)。但实际上你应该通过

在加密之上执行此操作

a) 在密文上计算 HMAC(例如 HMACSHA256),然后在解密时验证 HMAC(验证失败表示密码错误或数据已被篡改),或

b) 明文中有一段格式良好的数据可用于验证。

(A) 是更好的答案,原因有很多。


你有类似

的代码
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
var iv = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.IV = iv.GetBytes(AES.BlockSize / 8);

由于 keyiv 的密码、salt 和迭代次数相同,因此您的实际 IV 是加密密钥的前 128 位。如果您希望它是一个计算值,您需要改变盐值(例如,将盐值的最后一个字节设置为 0 用于加密密钥,1 用于 IV,以及 2 用于 HMAC 密钥),或将其作为下一个输出字节执行(要求 32 + 16 + 32 字节并相应地将其切碎......或者,给定 .NET 实现,调用 GetBytes(32), GetBytes(16 ), GetBytes(32)(Rfc2898DeriveBytes.GetBytes 是一个流API))。

或者,对于IV,使用加密时随机生成的IV,与密文一起传输即可。 (如果这样做,请务必将其包含在 HMAC 计算中)


您将 saltsaltBytes 存储在字段中(尽管您将一个设置为另一个,所以不清楚为什么有两个字段)。您需要将盐值与密文一起传输,并让解密器接受它作为输入。

基本上,您的函数应该是 Encrypt(passphrase, data) => <salt, hmac, ciphertext>(或 <salt, iv, hmac, ciphertext>)和 Decrypt(passphrase, salt, [iv,] hmac, ciphertext) => data。 (取决于 IV 是传输还是导出)