使用本机加密提供程序时.NET中的内存泄漏

时间:2012-03-02 15:41:53

标签: c# .net memory encryption

我有一个需要加密和解密字符串的应用程序。以下是我的解密方法:

  public static string Decrypt(string cipherText)
  {
     try
     {
        //Decrypt:
        byte[] keyArray;
        byte[] toDecryptArray = Convert.FromBase64String(cipherText);
        keyArray = UTF8Encoding.UTF8.GetBytes(key);
        AesCryptoServiceProvider Aes = new AesCryptoServiceProvider();
        Aes.Key = keyArray;
        Aes.Mode = CipherMode.CBC;
        Aes.Padding = PaddingMode.PKCS7;
        Aes.IV = IV;
        ICryptoTransform cTransform = Aes.CreateDecryptor();
        byte[] resultArray = cTransform.TransformFinalBlock(toDecryptArray, 0, toDecryptArray.Length);
        Aes.Clear();
        return UTF8Encoding.UTF8.GetString(resultArray, 0, resultArray.Length);
     }
     catch (Exception ex)
     {
        return "FAILED:*" + cipherText + "*" + ex.Message;
     }
  }

但是,这似乎泄漏了。如您所见,所有变量都是本地范围的,因此在完成此块时应该释放它们。作为背景,我调用这种方法有时几乎是不间断的。

为了确定我有泄漏我用很多请求淹没了我的服务器。通过仔细跟踪每个请求通过每个代码路径,我确定这段代码是罪魁祸首。当在我的记忆中评论时,使用率急剧上升,当被注释掉(并简单地返回cipherText)时,内存使用保持均匀。

5 个答案:

答案 0 :(得分:21)

AesCryptoServiceProvider实现IDisposable。尝试在using - 块中使用它。如下所示:

using(AesCryptoServiceProvider Aes = new AesCryptoServiceProvider()){
        Aes.Key = keyArray; 
        Aes.Mode = CipherMode.CBC; 
        Aes.Padding = PaddingMode.PKCS7; 
        Aes.IV = IV; 
        using (ICryptoTransform cTransform = Aes.CreateDecryptor()){
            byte[] resultArray = cTransform.TransformFinalBlock(toDecryptArray, 0, toDecryptArray.Length); 
            Aes.Clear(); 
            return UTF8Encoding.UTF8.GetString(resultArray, 0, resultArray.Length); 
        }
}

答案 1 :(得分:5)

通常,您应该始终处置实现IDisposable的对象,例如AesCryptoServiceProvider。它们通常使用非托管资源,这些资源不会被Garbagecollector清除。而不是

AesCryptoServiceProvider Aes = new AesCryptoServiceProvider();

using (AesCryptoServiceProvider Aes = new AesCryptoServiceProvider())
{
}

答案 2 :(得分:4)

  1. finally {} 块中处理Aes,因此即使发生异常也会将其处理掉。
  2. 弃置ICryptoTransform。由于ICryptoTransform IDisposableusing声明包裹在public static string Decrypt(string cipherText) { AesCryptoServiceProvider Aes; try { //Decrypt: byte[] keyArray; byte[] toDecryptArray = Convert.FromBase64String(cipherText); keyArray = UTF8Encoding.UTF8.GetBytes(key); Aes = new AesCryptoServiceProvider(); Aes.Key = keyArray; Aes.Mode = CipherMode.CBC; Aes.Padding = PaddingMode.PKCS7; Aes.IV = IV; using (ICryptoTransform cTransform = Aes.CreateDecryptor()) { byte[] resultArray = cTransform.TransformFinalBlock(toDecryptArray, 0, toDecryptArray.Length); Aes.Clear(); return UTF8Encoding.UTF8.GetString(resultArray, 0, resultArray.Length); } } catch (Exception ex) { return "FAILED:*" + cipherText + "*" + ex.Message; } finally { Aes.Dispose(); } } 声明中

    {{1}}

答案 3 :(得分:1)

正如其他地方所说,实现IDisposable的对象不会立即收集垃圾。如果您没有显式地或通过将其包装在一个使用中而对一次性对象调用Dispose,则该对象将花费更长时间来收集垃圾。

如果您正在尝试,您应该考虑在try之外声明这些变量并在finally块中实现处理。特别是在AesCryptoServiceProvider中,您希望确保即使发生错误也会执行Clear()方法,因为使用不会为您执行此操作。

public static string Decrypt(string cipherText)
{
   string decryptedMessage = null;
   AesCryptoServiceProvider Aes = null;
   ICryptoTransform cTransform = null;

   try
   {
      //Decrypt:
      byte[] keyArray = UTF8Encoding.UTF8.GetBytes(key);
      byte[] toDecryptArray = Convert.FromBase64String(cipherText);

      AesCryptoServiceProvider Aes = new AesCryptoServiceProvider();
      Aes.Key = keyArray;
      Aes.Mode = CipherMode.CBC;
      Aes.Padding = PaddingMode.PKCS7;
      Aes.IV = IV;

      ICryptoTransform cTransform = Aes.CreateDecryptor();

      byte[] resultArray = cTransform.TransformFinalBlock(toDecryptArray, 0, toDecryptArray.Length);
      decryptedMessage = UTF8Encoding.UTF8.GetString(resultArray, 0, resultArray.Length);
   }
   catch (Exception ex)
   {
      decryptedMessage = "FAILED:*" + cipherText + "*" + ex.Message;
   }
   finally
   {
       if (cTransform != null)
       {
           cTransform.Dispose();
       }

       if (Aes != null)
       {
           Aes.Clear();
           Aes.Dispose();
       }
   }

   return decryptedMessage;
}

你还应该考虑让异常抛出,通过消除catch块并保持finally,并在此方法之外处理它。

您还可以返回bool表示成功/失败,并使用out传递解密的字符串。这样,您就不会将错误与邮件内容混淆:

public bool string Decrypt(string cipherText, out string decryptedMessage)
{
   bool succeeded = false;
   decryptedMessage = null;
   AesCryptoServiceProvider Aes = null;
   ICryptoTransform cTransform = null;

   try
   {
      //Decrypt:
      byte[] keyArray = UTF8Encoding.UTF8.GetBytes(key);
      byte[] toDecryptArray = Convert.FromBase64String(cipherText);

      AesCryptoServiceProvider Aes = new AesCryptoServiceProvider();
      Aes.Key = keyArray;
      Aes.Mode = CipherMode.CBC;
      Aes.Padding = PaddingMode.PKCS7;
      Aes.IV = IV;

      ICryptoTransform cTransform = Aes.CreateDecryptor();

      byte[] resultArray = cTransform.TransformFinalBlock(toDecryptArray, 0, toDecryptArray.Length);
      decryptedMessage = UTF8Encoding.UTF8.GetString(resultArray, 0, resultArray.Length);
      succeeded = true;
   }
   catch (Exception ex)
   {
      decryptedMessage = "FAILED:*" + cipherText + "*" + ex.Message;
   }
   finally
   {
       if (cTransform != null)
       {
           cTransForm.Dispose();
       }

       if (Aes != null)
       {
           Aes.Clear();
           Aes.Dispose();
       }
   }

   return succeeded;
}

答案 4 :(得分:0)

我有一个非常类似的问题,在我应用了讨论中的所有提示后,这个问题并没有消失。该问题直接指向新的AesCryptoServiceProvider()出现-在此方法中,它总是由于内存不足而崩溃,如果我注释掉该块,则不会发生(如原始帖子中所述)。

事实证明,问题出在我组织代码的方式上。在一次调用TransformFinalBlock的过程中,我将哈希应用于整个字节缓冲区,在我看来,这非常大-500,000,000字节。

当我切换到使用多个TransformBlock和TransformFinalBlock的组合时,一切运行良好。这是我的最终代码(也许您会找到一种更优雅的编写循环的方式,其中在流的最后部分恰好一次调用TransformFinalBlock):

var bytes = new byte[4 * 1024];
int lastPortionSize;

using (var cryptographer = new SHA256CryptoServiceProvider()) {
    var byteCount = 0;
    while (true) {
        lastPortionSize = sourceStream.Read(bytes, 0, bytes.Length);
        byteCount += lastPortionSize;
        if (byteCount < totalSize) {
            cryptographer.TransformBlock(bytes, 0, lastPortionSize, bytes, 0);
        } else {
            break;
        }
    }
    cryptographer.TransformFinalBlock(bytes, 0, lastPortionSize);
    var hashBytes = cryptographer.Hash;
}