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

C# 使用本机加密提供程序时.NET内存泄漏,c#,.net,memory,encryption,C#,.net,Memory,Encryption,我有一个需要加密和解密字符串的应用程序。下面是我的解密方法: public static string Decrypt(string cipherText) { try { //Decrypt: byte[] keyArray; byte[] toDecryptArray = Convert.FromBase64String(cipherText); keyArray = UTF8Encoding.UTF

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

  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;
     }
  }
然而,这似乎是泄漏。正如您所看到的,所有变量都是局部作用域,因此在完成此块时应该释放它们。作为背景,我有时几乎不间断地调用这个方法

为了确定是否存在漏洞,我向服务器发送了大量请求。通过仔细跟踪每个请求所使用的每一条代码路径,我确定这段代码是罪魁祸首。当在我的内存中被注释时,使用量会急剧线性上升,而当被注释掉(并简单地返回密文)时,内存使用量会保持平衡

  • Dispose
    Aes
    finally{}块中,因此即使发生异常,它也会被释放
  • Dispose
    ICryptoTransform
    。由于
    ICryptoTransform
    IDisposable
    可以使用
    语句将其包装在
    中并进行处理

    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();
       }
    }
    

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

    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); 
            }
    }
    

    通常,您应该始终处理实现IDisposable的对象,例如AESCryptServiceProvider。它们通常使用非托管资源,垃圾收集器不会清除这些资源。所以不是

    AesCryptoServiceProvider Aes = new AesCryptoServiceProvider();
    


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

    如果你使用一个尝试,你应该考虑在尝试之外声明这些变量,并在最后一个块中实现处理。特别是对于AESCryptServiceProvider,您希望确保即使在发生错误时也执行Clear()方法,因为using不会为您这样做

    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块并保持最后,并在这个方法之外处理它。

    您还可以返回一个bool作为success/fail,并使用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;
    }
    

    我有一个非常相似的问题,在我应用了讨论中的所有提示后,这个问题并没有消失。这个问题直接指向新的AESCryptServiceProvider()——在这个方法中,它总是在内存不足的情况下崩溃,如果我注释掉这个块(就像在原来的帖子中一样),它就不会发生

    事实证明,问题在于我如何组织代码。在对TransformFinalBlock的一次调用中,我将哈希应用于整个字节缓冲区,在我的例子中,它相当大——500000000字节

    当我切换到使用多个TransformBlock和TransformFinalBlock的组合时,一切都很好。下面是我的最后一段代码(也许您会找到一种更优雅的方法来编写循环,其中TransformFinalBlock只在流的最后一部分调用一次):

    var bytes=新字节[4*1024];
    int lastPortionSize;
    使用(var cryptographer=new SHA256CryptoServiceProvider()){
    var字节数=0;
    while(true){
    lastPortionSize=sourceStream.Read(字节,0,字节.长度);
    字节数+=最后的端口大小;
    if(字节数<总大小){
    TransformBlock(字节,0,lastPortionSize,字节,0);
    }否则{
    打破
    }
    }
    TransformFinalBlock(字节,0,lastPortionSize);
    var hashBytes=cryptographer.Hash;
    }
    
    返回ex.Message的错误做法。让异常传播,这样您就一定知道发生了什么不好的事情。如果AESCryptServiceProvider和转换被包装在using()中,您会得到泄漏吗?@JohnSaunders是的。一切都处于日志记录/调试模式,试图查明我的问题。您是否100%确定此方法存在内存泄漏?你用什么来分析内存泄漏?您是否尝试过在返回值之前强制清除此方法中的局部变量?你做了什么来验证这个问题?它很难捕捉到这样一个简单的bug,它将存在于AsSuthSotoCeVIEW()中,考虑它的用法和年龄。我想暗示的是,这不太可能是您的泄漏源。@kmark2-如何处理返回值。我怀疑在这种方法之前。因为它真的什么都不是。我还建议使用语句。因此,请更新您的代码,分析更新后的代码,然后报告。请使用适当的内存配置文件软件。ICryptoTransform还实现IDisposable,因此需要将其包装为well@PeskyGnat:好的,我已经插入了这个-谢谢。
    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;
    }
    
    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;
    }