Make hash helpers thread-safe (fixes #72)#141
Open
zejji wants to merge 2 commits into
Open
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ByteArrayExtensionsandStreamExtensionscompute hashes through single, process-wide staticHashAlgorithminstances:A
HashAlgorithmis not thread-safe, so calling these helpers from multiple threads throws:This is easy to hit in practice because
InMemoryBlobStorage.Writehashes every write viadata.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:
MD5.HashData/SHA256.HashData.using(consistent with the existingHMACSHA256helper 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, andStreamExtensions.MD5.Tests
Adds:
ByteArrayExtensions.MD5/SHA256andStreamExtensions.MD5, asserting the canonical vectors for"The quick brown fox jumps over the lazy dog".byte[]helpers (Hash_helpers_can_be_called_concurrently, coveringMD5andSHA256) and theStreamhelper (MD5_can_be_called_concurrently). Two threads hash concurrently, each looping enough that they reliably overlap insideComputeHash(the race only trips while both threads are inside it at the same moment). These fail on the unfixed code (reproduced theCryptographicExceptionin 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#ifbuild.Fixes #72.