【问题标题】:PasswordDeriveBytes (System.Security.Cryptography) fails if disposed如果处置 PasswordDeriveBytes (System.Security.Cryptography) 将失败
【发布时间】:2012-03-08 06:54:57
【问题描述】:

如果第二次使用该类,则在 using 块中使用 PasswordDeriveBytes 类(因为它实现 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 类中分配第一个参数(从中派生密钥的密码),而该密码是通过 Byte 数组给出的。如果它作为一个字符串给出(因为过载)它工作正常。

助手 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() ,就像 salt 数组一样。这是一个明确的错误。

我是对的,还是我做错了什么?

编辑:

*用这个改变 AES.Encrypt() 和 AES.Decrypt 方法的第一行就可以了:*

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

【问题讨论】:

  • 请阅读并理解代码。没有人会两次使用同一个对象。有不同的方法。
  • 您能发布异常详细信息,包括堆栈跟踪吗?
  • 你是对的,我可以验证这一点。很奇怪。
  • @Ramhou - 你完全偏离了轨道。当数组包含值类型(字节)时,没有浅/深复制。解决方法只是使用要使用和归零的密码数组的副本。
  • 请注意,PasswordDeriveBytes 有很多很多问题。您应该改用Rfc2898DeriveBytes。如果你使用它,最多使用 20 个字节。您似乎还隐式生成了一个随机 IV,然后就忘了它。

标签: c# .net security cryptography idisposable


【解决方案1】:

这当然是违反直觉和未记录的行为,尽管它是否是一个错误可能存在争议。基本上,当您将密码字节数组传递给构造函数时,PasswordDeriveBytes 实例将获得该数组的所有权。这类似于 StreamReader 获取传递给它的 Stream 所有权的方式,并在 Disposed 时将其 Dispose (此行为也是 criticized 在类似的基础上,这导致向 StreamReader 添加了一个布尔参数.NET 4.0 中的构造函数,可以防止底层流被释放)。

在传入之前克隆字节数组可能是您最好的选择。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-03
    • 2012-08-29
    相关资源
    最近更新 更多