Skip to content

Make hash helpers thread-safe (fixes #72)#141

Open
zejji wants to merge 2 commits into
robinrodricks:developfrom
zejji:fix/thread-safe-hash-helpers
Open

Make hash helpers thread-safe (fixes #72)#141
zejji wants to merge 2 commits into
robinrodricks:developfrom
zejji:fix/thread-safe-hash-helpers

Conversation

@zejji

@zejji zejji commented Jun 21, 2026

Copy link
Copy Markdown

Problem

ByteArrayExtensions and StreamExtensions compute hashes through single, process-wide static HashAlgorithm instances:

private static readonly Crypto.MD5 _md5 = Crypto.MD5.Create();
private static readonly Crypto.SHA256 _sha256 = Crypto.SHA256.Create();

A HashAlgorithm is not thread-safe, so calling these helpers from multiple threads throws:

System.Security.Cryptography.CryptographicException:
Concurrent operations from multiple threads on this type are not supported.

This is easy to hit in practice because InMemoryBlobStorage.Write hashes every write via data.MD5(), so writing blobs from multiple threads (parallel tests, concurrent request handling, etc.) fails intermittently. Reported in #72.

Fix

Hash with a per-call instance instead of a shared static:

  • On .NET 5+, use the allocation-free, thread-safe one-shot MD5.HashData / SHA256.HashData.
  • On netstandard2.0 / 2.1, create and dispose a per-call instance with using (consistent with the existing HMACSHA256 helper in the same file).

Only the instance lifecycle changes; the hash output is identical. The change covers all three affected helpers: ByteArrayExtensions.MD5, ByteArrayExtensions.SHA256, and StreamExtensions.MD5.

Tests

Adds:

  • Known-value tests for ByteArrayExtensions.MD5/SHA256 and StreamExtensions.MD5, asserting the canonical vectors for "The quick brown fox jumps over the lazy dog".
  • Concurrency regression tests for the byte[] helpers (Hash_helpers_can_be_called_concurrently, covering MD5 and SHA256) and the Stream helper (MD5_can_be_called_concurrently). Two threads hash concurrently, each looping enough that they reliably overlap inside ComputeHash (the race only trips while both threads are inside it at the same moment). These fail on the unfixed code (reproduced the CryptographicException in 30/30 runs) and pass with this fix, in a few tens of ms.

Verified the library compiles across every target framework (netstandard2.0;netstandard2.1;net7.0;net8.0;net9.0), so both branches of the #if build.

Fixes #72.

Gerard Howell added 2 commits June 21, 2026 13:10
ByteArrayExtensions and StreamExtensions hashed data through single
static MD5/SHA256 HashAlgorithm instances. A HashAlgorithm is not
thread-safe, so concurrent calls throw:

    System.Security.Cryptography.CryptographicException:
    Concurrent operations from multiple threads on this type are not
    supported.

This is easy to hit because InMemoryBlobStorage.Write hashes every blob
write via data.MD5(), so writing blobs from multiple threads fails.

Hash with a per-call instance instead. On .NET 5+ this uses the
allocation-free, thread-safe one-shot MD5/SHA256.HashData; on
netstandard it creates and disposes a per-call instance (matching the
existing HMACSHA256 helper). The hash output is unchanged.

Adds known-value tests for the byte[] MD5/SHA256 and Stream MD5 helpers,
and concurrency regression tests for the byte[] and Stream helpers that
fail on the unfixed code and pass with the fix.

Fixes robinrodricks#72.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MD5 hash calls are failing with dotnet 8, linux environment and multiple threads

1 participant