为什么HMACSHA256 ComputeHash不是线程安全的

时间:2017-12-15 12:19:52

标签: c# thread-safety

由于该类是Disposable,我不知何故认为它应该可以用作单例。 我应该如何安全地使用它?

        // Code uses Nuget package FluentAssertions
        var key = "supersecret";
        var keybytes = Encoding.UTF8.GetBytes(key);
        var hmac = new HMACSHA256(keybytes);

        var tokenBytes = Encoding.UTF8.GetBytes("tokentocompute");
        var expected = hmac.ComputeHash(tokenBytes);

        await Task.WhenAll(
            Enumerable.Range(0, 100).Select(i => Task.Run(() =>
            {
                var hash = hmac.ComputeHash(tokenBytes);
                // This throws most of the time
                hash.ShouldBeEquivalentTo(expected, $"{i}");
            }))
        );

我认为它不是HMACSHA1.ComputeHash() thread-safety question的副本,因为它专门讨论设置密钥的不同线程,而我在每次调用时都使用相同的密钥。重读之后,它可能是重复的。会等你们的意见。

1 个答案:

答案 0 :(得分:2)

来自MSDN:

  

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

即使这个段落出现在MSDN的每个类中,你也需要牢记这一点。

查看反编译的代码,它似乎在这里和那里使用了几个私有变量。由于它没有锁定,因此错误很快就会发生。

[HashAlgorithm.cs]
/// <summary>Represents the size, in bits, of the computed hash code.</summary>
protected int HashSizeValue;
/// <summary>Represents the value of the computed hash code.</summary>
protected internal byte[] HashValue;
/// <summary>Represents the state of the hash computation.</summary>
protected int State;

[...]

[HashAlgorithm.cs]
public byte[] ComputeHash(byte[] buffer)
{
  if (this.m_bDisposed)
    throw new ObjectDisposedException((string) null);
  if (buffer == null)
    throw new ArgumentNullException(nameof (buffer));
  this.HashCore(buffer, 0, buffer.Length);
  this.HashValue = this.HashFinal();
  byte[] numArray = (byte[]) this.HashValue.Clone();
  this.Initialize();
  return numArray;
}

我们最终在我们的代码中放置了一个using-block,每次重新创建hmac实例。性能类似于在我们的粗略测试中围绕它进行全局锁定。我们希望避免使用例如过度设计的东西。由于性能相当不错,所以会发生线索。

    await Task.WhenAll(
        Enumerable.Range(0, 100).Select(i => Task.Run(() =>
        {
            byte[] hash;
            using (var hma = new HMACSHA256(keybytes))
            {
                hash = hma.ComputeHash(tokenBytes);
            }
            //lock (this)
            //{
            //    hash = hmac.ComputeHash(tokenBytes); 
            //}       

            // Both ways achieved the desired results and performance was similar         
            hash.ShouldBeEquivalentTo(expected, $"{i}");
        }))
    );