【问题标题】:Why does SHA1.ComputeHash fail under high load with many threads?为什么 SHA1.ComputeHash 在多线程的高负载下会失败?
【发布时间】:2014-12-22 22:17:27
【问题描述】:

我发现我维护的一些代码存在问题。下面的代码有一个private static SHA1 成员(这是一个IDisposable,但因为它是static,所以它永远不会被最终确定)。然而,在压力下,这段代码会抛出一个异常,表明它已被关闭:

Caught exception.  Safe handle has been closed" 
Stack trace: Call stack where exception was thrown
at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success)
at System.Security.Cryptography.Utils.HashData(SafeHashHandle hHash, Byte[] data, Int32 cbData, Int32 ibStart, Int32 cbSize)
at System.Security.Cryptography.Utils.HashData(SafeHashHandle hHash, Byte[] data, Int32 ibStart, Int32 cbSize)
at System.Security.Cryptography.HashAlgorithm.ComputeHash(Byte[] buffer)

有问题的代码是:

internal class TokenCache
{
    private static SHA1 _sha1 = SHA1.Create();

    private string ComputeHash(string password)
    {
        byte[] passwordBytes = UTF8Encoding.UTF8.GetBytes(password);
        return UTF8Encoding.UTF8.GetString(_sha1.ComputeHash(passwordBytes));
    }

我的问题显然是什么可能导致这个问题。对SHA1.Create 的调用能否静默失败(有多少加密资源可用)?会不会是 appdomain 宕机了?

还有其他理论吗?

【问题讨论】:

  • 这与 Dispose 有什么关系?另外,那是哪个“SHA1”类?
  • 你确定 SHA1 类是线程安全的吗?当密码失败时,你能抓住被哈希的密码吗?
  • @John Saunders,对不起,你是对的。这与 Dispose 无关。我认为 System.Security.Cryptography.SHA1CryptoServiceProvider 上的终结器可能已被某种方式触发。 msdn.microsoft.com/en-us/library/e7hyyd4e(v=vs.110).aspx
  • @user18044 我刚刚在本地(10,000)个随机哈希值上进行了压力测试。它工作得很好。将其更改为并行会导致您收到完全相同的错误(安全手柄已关闭)。我很确定这是因为在某个地方,您的应用程序是线程化的。
  • @user18044 下面重现了您的错误:var strings = Enumerable.Range(1,10000).Select(r => Guid.NewGuid().ToString()).ToList(); Parallel.ForEach(strings, s => { ComputeHash(s).Dump(); }); 使用 foreach,但是,不会

标签: c# sha stress-testing finalizer


【解决方案1】:

根据the documentationHashAlgorithm 基类

此类型的任何公共静态(在 Visual Basic 中为共享)成员都是线程安全的。不保证任何实例成员都是线程安全的。

您不应该在不同线程尝试同时在同一实例上调用ComputeHash 的线程之间共享这些类。

编辑 这就是导致您的错误的原因。由于多个线程在同一个哈希算法实例上调用 ComputeHash,下面的压力测试会产生各种错误。你的错误就是其中之一。

具体来说,我在这个压力测试中看到了以下错误:

  • System.Security.Cryptography.CryptographicException:哈希在指定状态下无效。
  • System.ObjectDisposedException:安全句柄已关闭

压力测试代码示例:

const int threadCount = 2;
var sha1 = SHA1.Create();
var b = new Barrier(threadCount);
Action start = () => {
                    b.SignalAndWait();
                    for (int i = 0; i < 10000; i++)
                    {
                        var pwd = Guid.NewGuid().ToString();
                        var bytes = Encoding.UTF8.GetBytes(pwd);
                        sha1.ComputeHash(bytes);
                    }
                };
var threads = Enumerable.Range(0, threadCount)
                        .Select(_ => new ThreadStart(start))
                        .Select(x => new Thread(x))
                        .ToList();
foreach (var t in threads) t.Start();
foreach (var t in threads) t.Join();

【讨论】:

  • 重大事故,保险柜手柄看起来不错。但不可否认,很好的答案。
  • @HansPassant。谢谢你。我猜可能是SHA1CryptoServiceProvider.Initialize 方法,它似乎做了一个非线程安全的Dispose,然后在_safeHashHandle 字段上重新创建。
  • 呵呵,奇怪,Initialize() 是在 计算哈希后调用的。一定是某种安全的东西。你明白了。
  • @HansPassant。是的,最好叫Reset。在进一步的检查中,MD5CryptoServiceProvider 的作用完全相同,其他的内部状态也有其他变体,它们会产生类似的效果。带回家绝对是任何HashAlgorithm 子类上的实例方法都不是线程安全的。
  • 公平点..下次我会检查日期?讨论的异常仍然存在?
猜你喜欢
  • 2012-07-06
  • 2023-03-12
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-07
  • 1970-01-01
相关资源
最近更新 更多