PasswordDeriveBytes (System.Security.Cryptography) завершается ошибкой при удалении - PullRequest
4 голосов
/ 08 марта 2012

Использование класса PasswordDeriveBytes в блоке using (который удаляет его, потому что он реализует IDisposable) создает проблему, если класс используется во второй раз. Это код:

public class AES
{
    protected static CryptoData localCryptoData;

    static AES()
    {
        localCryptoData = new CryptoData();
    }

    public static string Encrypt(CryptoData cryptoData)
    {
        using (PasswordDeriveBytes pass = new PasswordDeriveBytes(cryptoData.Password, cryptoData.Salt, "SHA1", 2))
        using (RijndaelManaged symmetricKey = new RijndaelManaged())
        {
            byte[] keyBytes = pass.GetBytes(cryptoData.KeySize / 8);
            symmetricKey.Padding = PaddingMode.PKCS7;
            symmetricKey.Mode = CipherMode.CBC;

            using (ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, cryptoData.InitVector))
            using (MemoryStream memoryStream = new MemoryStream())
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
            {
                cryptoStream.Write(cryptoData.ByteText, 0, cryptoData.ByteText.Length);
                cryptoStream.FlushFinalBlock();
                return Convert.ToBase64String(memoryStream.ToArray());
            }
        }
    }

    public static string Decrypt(CryptoData cryptoData)
    {
        using (PasswordDeriveBytes pass = new PasswordDeriveBytes(cryptoData.Password, cryptoData.Salt, "SHA1", 2))
        using (RijndaelManaged symmetricKey = new RijndaelManaged())
        {
            byte[] cipherTextBytes = Convert.FromBase64String(cryptoData.Text);
            byte[] keyBytes = pass.GetBytes(cryptoData.KeySize / 8);
            symmetricKey.Padding = PaddingMode.PKCS7;
            symmetricKey.Mode = CipherMode.CBC;

            using (ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, cryptoData.InitVector))
            using (MemoryStream memoryStream = new MemoryStream(cipherTextBytes))
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
            {
                byte[] textBytes = new byte[cipherTextBytes.Length];
                int count = cryptoStream.Read(textBytes, 0, textBytes.Length); //throws CryptographicException - Padding is invalid and cannot be removed.
                return Encoding.UTF8.GetString(textBytes, 0, count);
            }
        }
    }

Если этот класс используется таким образом:

AES.Encrypt (cryptoData); AES.Decrypt (cryptoData);

При первом использовании вы получаете правильную зашифрованную строку AES, но в случае неудачи с исключением при попытке расшифровать ту же строку. Проблема заключается в назначении первого параметра (пароля для получения ключа) из класса PasswordDeriveBytes, когда этот пароль передается через байтовый массив. Если он задан в виде строки (из-за перегрузки), он работает нормально.

Вспомогательный класс CryptoData:

public class CryptoData
{
    private string text;
    public string Text
    {
        get { return text; }
        set
        {
            text = value;

            if (value != null)
            {
                ByteText = Encoding.ASCII.GetBytes(value);
            }
            else
            {
                ByteText = null;
            }
        }
    }

    public byte[] ByteText { get; private set; }
    public byte[] Password { get; set; }
    public int KeySize { get; set; }
    public byte[] InitVector { get; set; }
    public byte[] Salt { get; set; }
}

Если вы просто измените эту строку в методах:

using (PasswordDeriveBytes pass = new PasswordDeriveBytes(cryptoData.Password, 
      cryptoData.Salt, "SHA1", 2))

в

using (PasswordDeriveBytes pass = new PasswordDeriveBytes("somePassword", 
         cryptoData.Salt, "SHA1", 2))

все отлично работает. Проблема в том, что экземпляр PasswordDeriveBytes не получает байтовый массив для пароля во второй раз, из-за оператора using. Если вместо байтового массива передана строка, она работает.

Редактировать: После более подробного рассмотрения, кажется, что есть проблема в установщике свойств по умолчанию для параметра пароля. Он получает указатель массива, и поэтому он его удаляет. Он должен сделать value.clone () из массива, как в случае с массивом соли. Это определенная ошибка.

Я прав или я что-то не так делаю?

Редактировать:

* Измените первую строку в методах AES.Encrypt () и AES.Decrypt с этим, и это работает: *

 using (PasswordDeriveBytes pass = new PasswordDeriveBytes(
        (byte[])cryptoData.Password.Clone(), 
        cryptoData.Salt, "SHA1", 2))

1 Ответ

1 голос
/ 01 апреля 2012

Это, безусловно, нелогичное и недокументированное поведение, хотя можно ли спорить об этом или нет.По сути, когда вы передаете байтовый массив пароля в конструктор, экземпляр PasswordDeriveBytes становится владельцем этого массива.Это похоже на то, как StreamReader становится владельцем потока, который ему передается, и будет утилизировать его при его удалении (это поведение также подвергалось критике по аналогичным причинам, что привело к добавлению логического значенияпараметр конструктора StreamReader в .NET 4.0, который может помешать расположению базового потока.)

Лучше всего клонировать массив байтов перед его передачей.

...