.NET 加密类的线程安全性?

发布于 2024-07-25 05:41:55 字数 1488 浏览 7 评论 0 原文

我的高级目标是创建一个静态实用程序类来封装我的 .NET 应用程序的加密。 在内部我想尽量减少不必要的对象创建。

我的问题是:在 .NET Framework 中实现对称加密的类的线程安全性是什么?特别是 System.Security.Cryptography.RijndaelManaged它生成的 ICryptoTransform 类型。

例如,在我的类构造函数中,我可以简单地执行以下操作吗?

static MyUtility()
{
    using (RijndaelManaged rm = new RijndaelManaged())
    {
        MyUtility.EncryptorTransform = rm.CreateEncryptor(MyUtility.MyKey, MyUtility.MyIV);
        MyUtility.DecryptorTransform = rm.CreateDecryptor(MyUtility.MyKey, MyUtility.MyIV);
    }
}

回避这个类中存在 Key 和 IV 是否安全的问题,这个示例块提出了许多其他问题:

  1. 我可以不断重用 EncryptorTransform 和 DecryptorTransform 吗?超过? *.CanReuseTransform*.CanTransformMultipleBlocks 属性暗示“是”,但是有什么我应该注意的警告吗?

  2. 由于RijndaelManaged实现了IDisposable,我倾向于将其放在using块中,特别是因为它可能与外部操作系统级库相关联。 由于我保留了 ICryptoTransform 对象,因此有任何警告吗?

  3. 可能是最重要的问题,在高度多线程环境中,我是否会遇到在线程之间共享 ICryptoTransform 对象的问题?

  4. 如果问题 3 的答案是它不是线程安全的,那么当我使用 ICryptoTransform 对象时,我是否会因锁定而导致性能严重下降? (取决于我认为的负载。)

  5. 每次简单地实例化新的 RijndaelManaged 性能会不会更高? 或者存储一个 RijndaelManaged 并每次生成 new RijndaelManaged().CreateEncryptor(...)

我希望有人知道这些在幕后是如何工作的,或者对类似实现中的问题有经验。 我发现,许多此类性能和线程相关问题通常只有在负载达到相当大的情况时才会显现出来。

谢谢!

I have a high-level goal of creating a static utility class that encapsulates the encryption for my .NET application. Inside I'd like to minimize the object creations that aren't necessary.

My question is: what is the thread-safety of the classes which implement symmetric encryption within the .NET Framework? Specifically System.Security.Cryptography.RijndaelManaged and the ICryptoTransform types it generates.

For instance, in my class constructor can I simply do something along the following lines?

static MyUtility()
{
    using (RijndaelManaged rm = new RijndaelManaged())
    {
        MyUtility.EncryptorTransform = rm.CreateEncryptor(MyUtility.MyKey, MyUtility.MyIV);
        MyUtility.DecryptorTransform = rm.CreateDecryptor(MyUtility.MyKey, MyUtility.MyIV);
    }
}

Side-stepping the issue of is it secure to have Key and IV exist within this class, this example block brings up a number of other questions:

  1. Can I continually reuse the EncryptorTransform and DecryptorTransform over and over? The *.CanReuseTransform and *.CanTransformMultipleBlocks properties imply "yes", but are there any caveats I should be aware of?

  2. Since RijndaelManaged implements IDisposable my inclination is to put it within a using block especially since it probably ties into external OS-level libs. Are there any caveats with this since I'm keeping the ICryptoTransform objects around?

  3. Potentially the most important question, in a highly multithreaded environment, will I run into issues with sharing the ICryptoTransform objects between threads?

  4. If the answer to #3 is that it isn't thread-safe, will I experience serious performance degradation from locking while I use the ICryptoTransform objects? (Depends on load I suppose.)

  5. Would it be more performant to simply instantiate new RijndaelManaged each time? Or store one RijndaelManaged and generate new RijndaelManaged().CreateEncryptor(...) each time?

I am hoping that someone out there knows how these work under the hood or are experienced with issues from similar implementations. I've found that a lot of these kinds of performance and thread-related issues typically do not manifest themselves until there is a sizable amount of load.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

往事随风而去 2024-08-01 05:41:55

1)是的。

2)一旦废弃,就无法再使用。 在此之前,您可以共享/使用它(但请参见下文)

3-4) 来自 MSDN

则不保证任何实例成员都是线程安全的。

“此类型的任何公共静态(在 Visual Basic 中共享)成员都是线程安全的。”如果您想保留这一点, ,并在线程之间共享它,您需要实现锁定并将其视为锁定资源。 否则,我建议根据需要制作单独的版本,并在完成后将其丢弃。

5)我建议根据需要创建这些,然后在以后发现性能问题时尝试优化它。 不要担心创建新版本对性能的影响,直到您在分析后发现这是一个问题。

1) Yes.

2) One you dispose of it, you cannot use it. Up until then, you can share/use it (but see below)

3-4) From MSDN:

"Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe. "

If you want to keep this around, and share it between threads, you'll need to implement locking and treat it as a locked resource. Otherwise, I'd recommend just making separate versions as needed, and disposing of them when you're done.

5) I would recommend creating these as needed, and then trying to optimize it if later you find you have a performance problem. Don't worry about the performance implications of creating a new version until you see that's its a problem after profiling.

梦幻之岛 2024-08-01 05:41:55

人们可以简单地使用基于并发堆栈的缓存来解决并发问题:

static ConcurrentStack<ICryptoTransform> decryptors = new ConcurrentStack<ICryptoTransform>();

void Encrypt()
{
   // Pop decryptor from cache...
   ICryptoTransform decryptor;
   if (!decryptors.TryPop(out decryptor))
   {
       // ... or create a new one since cache is depleted
       AesManaged aes = new AesManaged();
       aes.Key = key;
       aes.IV = iv;
       decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
    }

    try
    {
       //// use decryptor
    }
    finally
    {
       decryptors.Push(decryptor);
    }
 }

One could solve the concurrency problem simply with a cache based on a concurrent stack:

static ConcurrentStack<ICryptoTransform> decryptors = new ConcurrentStack<ICryptoTransform>();

void Encrypt()
{
   // Pop decryptor from cache...
   ICryptoTransform decryptor;
   if (!decryptors.TryPop(out decryptor))
   {
       // ... or create a new one since cache is depleted
       AesManaged aes = new AesManaged();
       aes.Key = key;
       aes.IV = iv;
       decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
    }

    try
    {
       //// use decryptor
    }
    finally
    {
       decryptors.Push(decryptor);
    }
 }
老街孤人 2024-08-01 05:41:55
  1. 绝对不,我有麻烦。 我已经使用它两年了,突然我的一些最重要的代码在解密时开始抛出错误。
    微软补丁的结果? 迈克菲? 点网框架更新? (框架 4.7.2)

当我最终弄清楚时,我将 CreateEncryptor 和 CreateDecryptor 移动到实际调用中的 using 中以进行加密和解密。 问题已解决。

  1. Absolutely NOT, I had troubles. I had been using it for two years when all of a sudden some of my most important code started throwing errors when decrypting.
    Result of Microsoft patches? McAfee? dot net framework updates? (framework 4.7.2)

When I finally figured it out I moved the CreateEncryptor and CreateDecryptor into a using inside the actual call to encrypt and decrypt. Problem fixed.

汐鸠 2024-08-01 05:41:55

我一直在努力解决这个与线程安全相关的问题,但我刚刚发现它是不同的。 这个“解决方案”与OP问题并不真正相关,但这篇文章是我在寻找问题解决方案时发现的最接近的文章,因此我将在这里发布,也许它会对其他人有所帮助。

当像这样使用 CryptoStream 时:

 using (MemoryStream memoryStream = new MemoryStream())
 using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Write))
 {
    cryptoStream.Write(cipherData, 0, cipherData.Length);
    cryptoStream.FlushFinalBlock();
    result = memoryStream.ToArray();
 }

如果 cipherData 不正确(例如,内容是纯文本且未加密),则可能会导致 FlushFinalBlock() 出现期望,例如:

"The input data is not a complete block." (System.Security.Cryptography.CryptographicException)

因此,某些内容将保持未完成状态,并导致下一次使用此函数(使用相同的解密器)返回的结果带有先前使用的剩余部分,从而导致稍后处理结果时出现意外错误。

正如 @Doug Hill 建议的那样,在每次调用时创建一个新的 CreateEncryptor()/CreateDecryptor() 将会修复它,因为这是函数每次调用之间的链接,但会以性能为代价。

在 @Andreas 的基本代码的帮助下,我创建了这个解决方案,仅在引发异常时才会处理这些 ICryptoTransform 对象,因为我不知道如何清除/重置它以再次重用。 它并不完美,但应该可以提高性能。

private Aes _aes;
private static ConcurrentStack<ICryptoTransform> decryptors = new ConcurrentStack<ICryptoTransform>();

public void Initialize()
{
    _aes = Aes.Create();
    // ...
}

public byte[] Decode(byte[] cipherData)
{
    var decryptor = DecryptorPop();
    
    try
    {
        byte[] result = null;
        using (MemoryStream memoryStream = new MemoryStream())
        using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Write))
        {
            cryptoStream.Write(cipherData, 0, cipherData.Length);
            cryptoStream.FlushFinalBlock();
            result = memoryStream.ToArray();
        }
        return result;
    }
    catch (Exception ex)
    {
        // since I do not know how to fix the decryptor I will just dispose it
        decryptor.Dispose();
        decryptor = null;

        // throw the same exception again if you want to catch it outside
        System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw();
    }
    finally
    {
        DecryptorPush(decryptor);
    }

    return null;
}

private ICryptoTransform DecryptorPop()
{
    ICryptoTransform decryptor;
    if (!decryptors.TryPop(out decryptor))
    {
        decryptor = _aes.CreateDecryptor();
    }

    return decryptor;
}

private void DecryptorPush(ICryptoTransform decryptor)
{
    if(decryptor == null) return;

    decryptors.Push(decryptor);
}

I have been fighting with this problem that I tough was thread safety related but I just dicovered it was different. This "solution" is not really related with the OP questions but this post was the closest I found when searching for solutions for my problem, thus I will post here and maybe it will help someone else.

When using CryptoStream like this:

 using (MemoryStream memoryStream = new MemoryStream())
 using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Write))
 {
    cryptoStream.Write(cipherData, 0, cipherData.Length);
    cryptoStream.FlushFinalBlock();
    result = memoryStream.ToArray();
 }

If the cipherData is incorrect (the content is plain text and not encrypted for example) it may cause an expection at the FlushFinalBlock(), for example:

"The input data is not a complete block." (System.Security.Cryptography.CryptographicException)

Because of this something will stay unfinished and will cause the next usage of this function (using the same decryptor) to return a result with leftovers of the previous usage, thus causing unexpected errors handling the result later.

As @Doug Hill sugested the creation of a new CreateEncryptor()/CreateDecryptor() at every call will fix it since that is the link between each call of the function but at a cost of performance.

With the help of the base code from @Andreas I created this solution that will dispose those ICryptoTransform objects only when the exception is thrown because I have no idea how to clear/reset it to reuse again. Its not perfect but should improve performance.

private Aes _aes;
private static ConcurrentStack<ICryptoTransform> decryptors = new ConcurrentStack<ICryptoTransform>();

public void Initialize()
{
    _aes = Aes.Create();
    // ...
}

public byte[] Decode(byte[] cipherData)
{
    var decryptor = DecryptorPop();
    
    try
    {
        byte[] result = null;
        using (MemoryStream memoryStream = new MemoryStream())
        using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Write))
        {
            cryptoStream.Write(cipherData, 0, cipherData.Length);
            cryptoStream.FlushFinalBlock();
            result = memoryStream.ToArray();
        }
        return result;
    }
    catch (Exception ex)
    {
        // since I do not know how to fix the decryptor I will just dispose it
        decryptor.Dispose();
        decryptor = null;

        // throw the same exception again if you want to catch it outside
        System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw();
    }
    finally
    {
        DecryptorPush(decryptor);
    }

    return null;
}

private ICryptoTransform DecryptorPop()
{
    ICryptoTransform decryptor;
    if (!decryptors.TryPop(out decryptor))
    {
        decryptor = _aes.CreateDecryptor();
    }

    return decryptor;
}

private void DecryptorPush(ICryptoTransform decryptor)
{
    if(decryptor == null) return;

    decryptors.Push(decryptor);
}
无声无音无过去 2024-08-01 05:41:55

重用从调用 AesManaged.CreateDecryptor 返回的 ICryptoTransform 实例意味着您使用相同的密钥和初始化向量来解密先前使用相同密钥加密的多条消息和初始化向量。

这是一个非常非常糟糕的主意。

IV 永远不应该被重复使用,并且密钥只能通过使用保护实际会话或消息密钥的主密钥来间接重复使用,该主密钥对于每条消息都应该是唯一的。 因此,唯一重复使用的密钥是主密钥,它仅用于加密其他密钥,而不用于加密数据。 密钥或 IV 之间也不应该有任何关系,即不要从密钥派生 IV,或从主密钥派生会话密钥等。

因此,要回答最初的和后续的问题,该问题归结为“如何为了安全地重用“解密器”对象”,答案很简单:不要这样做,因为那样你就重复使用了密钥和 IV,并且两者都不应该重复使用。

计划总是充满危险,一般来说我建议根本不要这样做。 只需看看各大公司和组织(微软的 Office 加密、IEEE WiFi 加密仅举两例)出错的次数,甚至多次。

这不是线程安全与否等问题,而是首先为什么要加密的问题。 如果您没有正确执行此操作,最好不要执行所有操作,因为这样至少可以清楚地看出消息不受保护。 做得不正确只不过是混淆视听。

Reusing the ICryptoTransform instance returned from a call to AesManaged.CreateDecryptor implies that you are using the same key and initialization vector to decrypt multiple messages, that were previously encrypted with the same key and initialization vector.

This is a very, very, bad idea.

IV's should never be re-used, and keys should only be re-used indirectly, by having a master key protecting the actual session or message key, which should be unique for every message. So the only re-used key is the master key, which is only used to encrypt other keys, never data. Nor should there be any relationship between the keys or the IV, i.e. don't derive the IV from the key or the session key from the master key etc.

Thus, to answer the original and subsequent question which boils to "How to safely re-use 'decryptor' objects", the answer is simply: don't because then you're re-using both the key and the IV, and neither should be re-used.

Rolling your own cryptographic schemes is always fraught with danger, and in general I'd recommend not to do it at all. Just look at the number of times major companies and organizations (Microsoft's Office encryption, IEEE WiFi encryption just to mention two) got it wrong, multiple times even.

It's not a question of thread safety or not etc., it's a question of why you're encrypting in the first place. If you're not doing it correctly, it's better not to do it all, since then at least it's clear that the messages are not protected. Doing it incorrectly amounts to no more than obfuscation.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文