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

Ale*_*AIT 0 c# thread-safety

由于该类是一次性的,我以某种方式认为它可能应该用作单例。我应该如何安全地使用它?

        // 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}");
            }))
        );
Run Code Online (Sandbox Code Playgroud)

我不认为它是HMACSHA1.ComputeHash() 线程安全问题的重复,因为它专门讨论设置密钥的不同线程,而我在每次调用中都使用相同的密钥。重读之后,它可能仍然是重复的。坐等各位大佬的意见。

Ale*_*AIT 7

来自 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;
}
Run Code Online (Sandbox Code Playgroud)

我们最终在我们的代码中放置了一个 using 块,每次都重新创建 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}");
        }))
    );
Run Code Online (Sandbox Code Playgroud)