Table of Contents

Bodu.IO.Hashing - Getting started

Install

dotnet add package Bodu.IO.Hashing

Targets net8.0. Depends on Bodu.Core and the BCL System.IO.Hashing package.

Minimal samples - one per subfamily

Checksum - CRC-32

using System.Text;
using Bodu.IO.Hashing.Checksums;

byte[] data = Encoding.UTF8.GetBytes("the quick brown fox");

using var crc = new Crc(CrcStandard.CRC32_ISOHDLC);
crc.Append(data);
string hex = Convert.ToHexString(crc.GetCurrentHash());

CRC32_ISOHDLC is the canonical zlib / PNG / Ethernet CRC-32. Swap it for CRC32_ISCSI, CRC16_MODBUS, CRC64_XZ, or any of the 112 entries in the CRC catalogue.

Checksum - Fletcher-32

using System.Text;
using Bodu.IO.Hashing.Checksums;

byte[] data = Encoding.UTF8.GetBytes("the quick brown fox");

using var fletcher = new Fletcher32();
fletcher.Append(data);
byte[] checksum = fletcher.GetCurrentHash();

Fletcher's twin-accumulator structure catches transpositions that a simple sum or XOR misses. Choose Fletcher16 / Fletcher32 / Fletcher64 based on your output width.

Fingerprint - FNV-1a 64

using System.Text;
using Bodu.IO.Hashing;

byte[] data = Encoding.UTF8.GetBytes("the quick brown fox");

using var fnv = new Fnv1a64();
fnv.Append(data);
ulong key = BitConverter.ToUInt64(fnv.GetCurrentHash());

Constant-memory and streaming. For SIMD-friendly throughput on large buffers, swap in CityHash64. For seeded hashes used in databases or probabilistic data structures, use MurmurHash3_128. For xxHash specifically, prefer System.IO.Hashing.XxHash64 from the BCL - Bodu does not duplicate it.

Fingerprint - Pearson with custom output width

using Bodu.IO.Hashing;

using var hash = new Pearson(hashSizeBits: 256, tableType: Pearson.PearsonTableType.Pearson);
hash.Append(data);
byte[] digest = hash.GetCurrentHash(); // 32 bytes

Pearson accepts any output width from 8 bits to 2048 bits in 8-bit steps; the nested Pearson.PearsonTableType enum selects one of the four built-in permutation tables, and a third constructor overload takes your own 256-byte permutation.

Check digit - Luhn (credit card)

using Bodu.IO.Hashing.CheckDigits;

bool valid = Luhn.IsValid("4539148803436467");   // payload includes the check digit
char digit = Luhn.Compute("453914880343646");    // payload excludes it - returns the digit to append

Every single-character scheme exposes the same pair: Compute(ReadOnlySpan<char>) returns the char to append to a payload that does not yet carry the check; IsValid(ReadOnlySpan<char>) validates a payload that does. Substitute Damm, Verhoeff, Ean13, Gtin14, UpcA, Isin, or AbaRoutingNumber - the contract is identical. The multi-character schemes (Iban, Lei) return a string from Compute instead.

Check digit - IBAN (multi-character)

using Bodu.IO.Hashing.CheckDigits;

bool valid = Iban.IsValid("GB82WEST12345698765432");

One-shot computation via the extension methods

using Bodu.IO.Hashing;
using Bodu.IO.Hashing.Extensions;

using var hash = new Fnv1a64();
byte[] digest   = hash.ComputeHash(data);            // Append + GetCurrentHash + Reset, in one call
bool   match    = hash.VerifyHash(data, digest);     // recompute and compare against the byte[]
bool   matchHex = hash.VerifyHash(data, Convert.ToHexString(digest)); // or compare against a stored hex string

ComputeHash resets the instance and returns the one-shot digest, so the same instance is immediately reusable. VerifyHash has overloads taking either a byte[] digest or a hex string, and Stream-based forms that hash the stream first. TryVerifyHash returns false instead of throwing when the candidate is malformed (wrong length, non-hex characters) - the safer choice over user-supplied input.

Important

VerifyHash compares with SequenceEqual and short-circuits on the first mismatching byte - it is not constant-time and must not be used to check an authenticator supplied by an untrusted caller. These are error-detection comparisons. For constant-time verification of a keyed digest, use the Bodu.Security.Cryptography VerifyHash overloads, which call CryptographicOperations.FixedTimeEquals.

Async streaming over a Stream

using Bodu.IO.Hashing;
using Bodu.IO.Hashing.Extensions;

await using FileStream fs = File.OpenRead("archive.bin");

using var hash = new Crc(CrcStandard.CRC32_ISOHDLC);
byte[] digest = await hash.ComputeHashAsync(fs);     // rents a pooled buffer, appends in chunks

AppendDataAsync, ComputeHashAsync, VerifyHashAsync, and TryVerifyHashAsync are the streaming-friendly equivalents; each accepts an optional bufferSize and a CancellationToken. They rent the working buffer from ArrayPool<byte>.Shared, so hashing a large file allocates only the final digest.

Where to go next