为什么SHA1.ComputeHash在很多线程的高负载下失败?

时间:2014-10-27 16:48:50

标签: c# sha stress-testing finalizer

我看到我维护的一些代码存在问题。下面的代码有一个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发生故障造成的吗?

还有其他理论吗?

1 个答案:

答案 0 :(得分:27)

根据HashAlgorithm基类的the documentation

  

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

您不应该在不同线程尝试同时在同一个实例上调用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();