Salt 中的什么导致解密失败

What in the Salt causes it to fail in decryption

下面和 Fiddle 中的代码不是用于生产,而是用于教育目的。我不想修复任何东西,因为我有一个可行的解决方案。但是,我想知道为什么:

var password = "password";
var salt = Encoding.ASCII.GetBytes(password.Length.ToString());
var secret = new PasswordDeriveBytes(password, salt);

当上面的实现后,下面的方法FixedEncryptor将起作用。

// Valid:
public static string FixedEncryptor(string content)
{
    var cipher = new RijndaelManaged();
    var plain = Encoding.Unicode.GetBytes(content);
    var key = new PasswordDeriveBytes(password, salt);
    using (var encrypt = cipher.CreateEncryptor(key.GetBytes(32), key.GetBytes(16)))
        using (var stream = new MemoryStream())
            using (var crypto = new CryptoStream(stream, encrypt, CryptoStreamMode.Write))
            {
                crypto.Write(plain, 0, plain.Length);
                crypto.FlushFinalBlock();
                return Convert.ToBase64String(stream.ToArray());
            }
}

但是,如果您实施:

var secret = new PasswordDeriveBytes("password",
     Encoding.ASCII.GetBytes("password"));

代码会突然产生:

Run-time exception (line 70): Padding is invalid and cannot be removed.

Stack Trace:

[System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed.] at Crypt.Decryptor(String content): line 70 at Program.Main(): line 17

如下方法表示:

// Invalid:
public static string Encryptor(string content)
{
    var cipher = new RijndaelManaged();
    var plain = Encoding.Unicode.GetBytes(content);
    var key = new PasswordDeriveBytes("password", Encoding.ASCII.GetBytes("password"));
    using (var encrypt = cipher.CreateEncryptor(key.GetBytes(32), key.GetBytes(16)))
        using (var stream = new MemoryStream())
            using (var crypto = new CryptoStream(stream, encrypt, CryptoStreamMode.Write))
            {
                crypto.Write(plain, 0, plain.Length);
                crypto.FlushFinalBlock();
                return Convert.ToBase64String(stream.ToArray());
            }
}

那为什么一个能解密成功,而另一个解密不正确,出现上述错误呢?

一个Fiddle的小例子是here.

首先,你生成盐的方法一点都不安全;其次,PasswordDerivedBytes 已弃用,您应该查看其继任者 Rfc2898DeriveBytes.

尝试类似以下的操作 - 请注意,这需要一些 using 语句:SystemSystem.IOSystem.Security.CryptographySystem.Text

简单地用Encrypt(PlainText, Password)加密数据并用Decrypt(EncryptedData, Password)再次解密。盐作为前 16 个字节卷入加密数据,并且在每个 encryption/decryption 轮中完全随机。

此代码是我自己的开源密码管理器的一部分。

/*
 * Encryption/Decryption, based on AES256 and PBKDF2
 */
public string Encrypt (string plainText, string passPhrase, bool fast_encrypt = false)
{
    string result;
    using (Rijndael algR = Rijndael.Create ()) {
        RNGCryptoServiceProvider rngC = new RNGCryptoServiceProvider ();
        byte[] iv = new byte[16];
        rngC.GetBytes (iv);
        Rfc2898DeriveBytes derived = new Rfc2898DeriveBytes (passPhrase, iv, fast_encrypt ? 10 : 3000);
        algR.KeySize = 256;
        algR.BlockSize = 128;
        algR.Key = derived.GetBytes (32);
        algR.IV = iv;
        using (MemoryStream memoryStream = new MemoryStream ()) {
            memoryStream.Write (iv, 0, 16);
            using (CryptoStream cryptoStreamEncrypt = new CryptoStream (memoryStream, algR.CreateEncryptor (algR.Key, algR.IV), CryptoStreamMode.Write)) {
                using (StreamWriter streamWriterEncrypt = new StreamWriter (cryptoStreamEncrypt)) {
                    streamWriterEncrypt.Write (plainText);
                }
            }

            result = Convert.ToBase64String (memoryStream.ToArray ());
        }
    }

    return result;
}

public string Decrypt (string cipherText, string passPhrase, bool fast_decrypt = false)
{
    string result;
    using (Rijndael algR = Rijndael.Create ()) {
        using (MemoryStream memoryStream = new MemoryStream (Convert.FromBase64String (cipherText))) {
            byte[] iv = new byte[16];
            memoryStream.Read (iv, 0, 16);
            Rfc2898DeriveBytes derived = new Rfc2898DeriveBytes (passPhrase, iv, fast_decrypt ? 10 : 3000);
            algR.KeySize = 256;
            algR.BlockSize = 128;
            algR.Key = derived.GetBytes (32);
            algR.IV = iv;
            using (CryptoStream cryptoStreamDecrypt = new CryptoStream (memoryStream, algR.CreateDecryptor (algR.Key, algR.IV), CryptoStreamMode.Read)) {
                using (StreamReader streamReaderDecrypt = new StreamReader (cryptoStreamDecrypt)) {
                    result = streamReaderDecrypt.ReadToEnd ();
                }
            }
        }
    }

    return result;
}

根据您发布的代码示例,您的问题来自于您使用了两种不同的盐。

在 FixedEncryptor 中,您使用

的盐
Encoding.ASCII.GetBytes(password.Length.ToString());

编码为等于 { 56 } 的字节数组,这是因为 Length returns 8 然后在 returns 上调用 ToString()您将其转换为 ascii 值 56 的字符串“8”。

在 Encryptor 中,您使用

的盐
Encoding.ASCII.GetBytes("password")

编码为字节数组等于{ 112, 97, 115, 115, 119, 111, 114, 100},即字符"p"、"a"、"s"、[=46=的ascii值]、"w"、"o"、"r" 和 "d"。

您 运行 遇到的问题是您只尝试在解密函数中使用 { 56 },所以您的问题归结为 您的加密函数和解密函数使用两种不同的盐.

如果我制作一个新的 Decrypter 以使用与 Encryptor 相同的盐和密码,然后制作一个单独的 FixedDecryptor 以匹配 FixedEncryptor 所有内容的盐会很好

public class Program
{
    public static void Main()
    {
        var message = "Hello World!";
        var fixedCipherText = Crypt.FixedEncryptor(message);
        var cipherText = Crypt.Encryptor(message);
        Console.WriteLine(cipherText);
        Console.WriteLine(fixedCipherText);
        var plainText = Crypt.Decryptor(cipherText);
        var fixedPlainText = Crypt.FixedDecryptor(fixedCipherText);
        Console.WriteLine(plainText);
        Console.WriteLine(fixedPlainText);
    }
}

public static class Crypt
{
    private const string password = "password";
    private readonly static byte[] salt = Encoding.ASCII.GetBytes(password.Length.ToString());
    public static string FixedEncryptor(string content)
    {
        var cipher = new RijndaelManaged();
        var plain = Encoding.Unicode.GetBytes(content);
        var key = new PasswordDeriveBytes(password, salt);
        using (var encrypt = cipher.CreateEncryptor(key.GetBytes(32), key.GetBytes(16)))
        using (var stream = new MemoryStream())
        using (var crypto = new CryptoStream(stream, encrypt, CryptoStreamMode.Write))
        {
            crypto.Write(plain, 0, plain.Length);
            crypto.FlushFinalBlock();
            return Convert.ToBase64String(stream.ToArray());
        }
    }

    public static string Encryptor(string content)
    {
        var cipher = new RijndaelManaged();
        var plain = Encoding.Unicode.GetBytes(content);
        var key = new PasswordDeriveBytes("password", Encoding.ASCII.GetBytes("password"));
        using (var encrypt = cipher.CreateEncryptor(key.GetBytes(32), key.GetBytes(16)))
        using (var stream = new MemoryStream())
        using (var crypto = new CryptoStream(stream, encrypt, CryptoStreamMode.Write))
        {
            crypto.Write(plain, 0, plain.Length);
            crypto.FlushFinalBlock();
            return Convert.ToBase64String(stream.ToArray());
        }
    }

    public static string FixedDecryptor(string content)
    {
        var cipher = new RijndaelManaged();
        var encrypted = Convert.FromBase64String(content);
        var key = new PasswordDeriveBytes(password, salt);
        using (var decryptor = cipher.CreateDecryptor(key.GetBytes(32), key.GetBytes(16)))
        using (var stream = new MemoryStream(encrypted))
        using (var crypto = new CryptoStream(stream, decryptor, CryptoStreamMode.Read))
        {
            byte[] plain = new byte[encrypted.Length];
            int decrypted = crypto.Read(plain, 0, plain.Length);
            string data = Encoding.Unicode.GetString(plain, 0, decrypted);
            return data;
        }
    }

    public static string Decryptor(string content)
    {
        var cipher = new RijndaelManaged();
        var encrypted = Convert.FromBase64String(content);
        var key = new PasswordDeriveBytes("password", Encoding.ASCII.GetBytes("password"));
        using (var decryptor = cipher.CreateDecryptor(key.GetBytes(32), key.GetBytes(16)))
        using (var stream = new MemoryStream(encrypted))
        using (var crypto = new CryptoStream(stream, decryptor, CryptoStreamMode.Read))
        {
            byte[] plain = new byte[encrypted.Length];
            int decrypted = crypto.Read(plain, 0, plain.Length);
            string data = Encoding.Unicode.GetString(plain, 0, decrypted);
            return data;
        }
    }
}

Fiddel的代码。

然而,这仍然不是 "correct" 做事的方式。请参阅 答案