jaytwo.HashSiphon
0.1.0-beta-20250915083103
dotnet add package jaytwo.HashSiphon --version 0.1.0-beta-20250915083103
NuGet\Install-Package jaytwo.HashSiphon -Version 0.1.0-beta-20250915083103
<PackageReference Include="jaytwo.HashSiphon" Version="0.1.0-beta-20250915083103" />
<PackageVersion Include="jaytwo.HashSiphon" Version="0.1.0-beta-20250915083103" />
<PackageReference Include="jaytwo.HashSiphon" />
paket add jaytwo.HashSiphon --version 0.1.0-beta-20250915083103
#r "nuget: jaytwo.HashSiphon, 0.1.0-beta-20250915083103"
#:package jaytwo.HashSiphon@0.1.0-beta-20250915083103
#addin nuget:?package=jaytwo.HashSiphon&version=0.1.0-beta-20250915083103&prerelease
#tool nuget:?package=jaytwo.HashSiphon&version=0.1.0-beta-20250915083103&prerelease
jaytwo.HashSiphon
jaytwo.HashSiphon is a .NET stream wrapper that computes a hash (SHA256, SHA1, MD5, or custom) while reading from or writing to a stream — without needing to buffer or read the stream twice.
Features
- Hash streams in real time (read or write)
- SHA256, SHA1, MD5, or custom algorithm support
- Works with Stream APIs like
CopyToAsync,WriteAsync, etc. - Async and cancellation token support
- Does not buffer or re-read the stream
- Hash available after stream is fully consumed
Background
Once or twice, I've accepted user uploads to an API which I then stream to an object store. Sometimes I want a hash of that file—whether for deduplication, verification, or future audit. Usually, however, the file isn't supplied with a hash up front.
This package provides a way to hash the content as it's being read from or written to the underlying stream.
Since we don't have the full hash until the stream is consumed (referring to the use case above), this doesn't allow us to set the Content-MD5 header (for example) before sending to the object store. It does, however, allow us to persist the final hash to a database or log it for tracking, all without reading the stream twice.
Installation
Add the NuGet package:
PM> Install-Package jaytwo.HashSiphon
Usage
Wrap the input stream with a HashSiphonStream, then read it as usual. The hash becomes available after the stream is fully consumed.
Examples
// Example: Hashing While Reading
using var input = File.OpenRead("myfile.txt");
using var hashStream = HashSiphonStream.CreateSHA256Read(input);
using var output = File.Create("copy.txt");
await hashStream.CopyToAsync(output);
// Final hash is now available
Console.WriteLine(hashStream.HashHex);
// Example: Hashing While Writing
using var output = File.Create("output.txt");
using var hashStream = HashSiphonStream.CreateSHA256Write(output);
var buffer = Encoding.UTF8.GetBytes("hello world");
await hashStream.WriteAsync(buffer, 0, buffer.Length);
// finalize the hash without disposing
await hashStream.FlushAsync(finalizeHash: true);
Console.WriteLine(hashStream.HashHex);
Factory Methods
| Method | Description |
|---|---|
CreateSHA256Read() |
Creates a SHA256 stream in read mode |
CreateSHA1Read() |
Creates a SHA1 stream in read mode |
CreateMD5Read() |
Creates a MD5 stream in read mode |
CreateSHA256Write() |
Creates a SHA256 stream in write mode |
CreateSHA1Write() |
Creates a SHA1 stream in write mode |
CreateMD5Write() |
Creates a MD5 stream in write mode |
CreateRead() |
Custom algorithm, read mode |
CreateWrite() |
Custom algorithm, write mode |
Custom Algorithms
If you want to use a custom HashAlgorithm:
// I used this approach to write multiple generated XML documents into a ZIP package (which requires a CRC32 checksum for each entry) when streaming an XLSX file to a write-only output.
using (var hashSiphonStream = HashSiphonStream.CreateWrite(_outputStream, () => new Crc32Algorithm(isBigEndian: false), leaveOpen: true))
{
await WriteXml(hashSiphon);
await hashSiphonStream.FlushAsync(finalizeHash: true);
crc32 = BitConverter.ToUInt32(hashSiphon.Hash);
}
All constructor overloads accept leaveOpen = true if you don't want the wrapped stream disposed when HashSiphonStream is disposed.
Notes
- Hash computation is finalized automatically when a read stream hits EOF or when a write stream is flushed with
finalizeHash = trueor disposed. Hash(andHashHex/HashBase64) will return null until the hash computation is finalized.- You can use any of the
TryGetHash()variants to check for hash availability and retrieve the value in a single line. These are mostly useful for scenarios where you're uncertain whether the hash has been finalized yet (e.g., mid-stream inspection). - Supports
DisposeAsync()on .NET 5+. SeekandSetLengthare not supported.
Made with ♥ by Jake — Licensed under the MIT License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.6.2
- No dependencies.
-
.NETStandard 2.0
- No dependencies.
-
net6.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-beta-20250915083103 | 266 | 9/15/2025 |
| 0.1.0-beta-20250512175120 | 284 | 5/12/2025 |
| 0.1.0-beta-20250509102925 | 111 | 5/9/2025 |
| 0.1.0-beta-20250505183914 | 183 | 5/6/2025 |
| 0.1.0-beta-20250502190539 | 97 | 5/3/2025 |