Table of Contents

Using hashes and checksums

Bodu.Security.Cryptography ships the library's keyed hashes (SipHash), one-time authenticators (Poly1305), cryptographic digests (Tiger, Snefru, CubeHash, Whirlpool, BLAKE2 / BLAKE3, Skein, ASCON-HASH256, ASCON-HASHA256), the XOFs (SHAKE, ASCON-XOF128 / CXOF128), and Merkle-tree hashing. The digests, MACs, and SHAKE plug into the standard HashAlgorithm contract; the ASCON XOFs expose an absorb / squeeze sponge surface, and the Merkle tree hashes are factory-constructed one-shot ComputeHash types over any inner HashAlgorithm.

This page is the cross-cutting overview - what guarantee a cryptographic hash actually makes, how the structural shapes (digest / XOF / tree) differ, which to choose for which job, and how to verify a digest safely. For the full per-algorithm walk-throughs, see:

Looking for CRC, Fletcher, Adler, FNV, CityHash, Pearson, Bernstein, BKDR, SDBM, JSHash, Elf64, ApHash, or Pjw32? Those non-cryptographic families live in the companion Bodu.IO.Hashing package, built on NonCryptographicHashAlgorithm. See the Bodu.IO.Hashing guides.

What a cryptographic hash guarantees

A cryptographic hash is a one-way function: cheap to compute forwards, computationally infeasible to invert. Three named resistance properties define the contract, and the security level of each is bounded by the digest width:

  • Pre-image resistance - given a digest h, no efficient way to find any input m with hash(m) = h. Costs ≈ 2ⁿ for an n-bit digest.
  • Second-pre-image resistance - given an input m₁, no efficient way to find a different m₂ with the same digest. Costs ≈ 2ⁿ.
  • Collision resistance - no efficient way to find any pair m₁ ≠ m₂ with the same digest. Costs ≈ 2^(n/2) by the birthday bound - so a 256-bit digest gives only 128-bit collision resistance, and truncating a digest halves the collision exponent with it.

This is the line between this package and the non-cryptographic Bodu.IO.Hashing fingerprints: those carry no adversary model and fail every property above against an attacker who can choose the input. A cryptographic digest provides integrity only when the digest itself reaches the verifier over an authenticated channel - on its own it is not authentication. For integrity and authenticity against an active attacker, reach for a keyed hash / MAC (below) or an AEAD mode.

The three structural shapes

The cryptographic hashes here share the HashAlgorithm base but differ in what they produce:

Shape Output Types Use
Plain digest Fixed width chosen at construction Tiger, Whirlpool, CubeHash, Snefru128, Blake2b, Skein512, AsconHash256 Content addressing, signature inputs, fingerprints.
Extendable output (XOF) Any requested length Shake, AsconXof128 Squeeze arbitrary-length key material or deterministic randomness from a seed.
Tree Root digest over hashed leaves MerkleTree Verifiable per-chunk inclusion proofs; partial re-verification.

Two families on this page take a secret key in addition to the message and produce an authentication tag - and they split on a critical axis:

Subtype Type Key reuse
Reusable PRF SipHash64, SipHash128 One key authenticates many messages.
One-time authenticator Poly1305 The key authenticates exactly one message - reuse is a complete break.

The HashAlgorithm lifecycle

Every cryptographic hash here is a HashAlgorithm, so the BCL contract applies unchanged:

  • One-shot - ComputeHash(byte[]), ComputeHash(ReadOnlySpan<byte>), or ComputeHash(Stream). The instance is auto-reinitialised afterwards, so the same instance can hash the next message.
  • Streaming - TransformBlock(...) repeatedly, then TransformFinalBlock(...); read the digest from the Hash property. Call Initialize() to reset between messages.
  • CanReuseTransform reports whether the instance survives a finalisation - true for the digests and for SipHash, false for Poly1305 (its way of refusing key reuse at the API level).

The HashAlgorithmExtensions helpers add AppendData, VerifyHash, and TryVerifyHash (and their async overloads) on top of that base.

Pick the right tool

If you need… Use Why
A fast non-cryptographic fingerprint for hash tables, bucketing, caches Adler32, Fnv1a32, Fnv1a64, CityHash64 Cheap, well-spread, not cryptographic.
A hash-table key or short fingerprint, resistant to collision-DoS SipHash64, SipHash128 Keyed, collision-resistant for short inputs.
A cryptographic digest for signatures, fingerprints, or content addressing Tiger, or System.Security.Cryptography.SHA256 (BCL) Collision-resistant against active attackers.
A NIST-standardized 256-bit digest with a small state footprint (SP 800-232) AsconHash256, AsconHashA256 Two variants: max margin (ASCON-HASH256) or higher throughput (ASCON-HASHA256).
A rolling integrity check over a long stream with partial re-verification MerkleTree Per-block inclusion proofs without rehashing the whole input.
An on-the-wire CRC (zlib, PNG, Modbus, iSCSI, …) or a Fletcher checksum Crc, Fletcher32 Non-cryptographic, System.IO.Hashing contract - see the Bodu.IO.Hashing guides.

Pattern 1 - a classic non-cryptographic fingerprint

Adler, FNV, and CityHash all derive from NonCryptographicHashAlgorithm (via Bodu.IO.Hashing), so they drop into any API that accepts a standard .NET non-cryptographic hash.

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

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

var fnv = new Fnv1a64();
fnv.Append(data);
byte[] digest = fnv.GetCurrentHash();
string hex    = Convert.ToHexString(digest);   // 8-byte value, 16 hex characters

The same shape works for Adler32, Adler64, Fletcher16, Fletcher32, Fletcher64, Fnv1a32, Fnv1a64, CityHash32, CityHash64, Crc, and the classic string hashes (Bernstein, BKDR, SDBM, JSHash, Elf64, ApHash, Pjw32, Pearson). They all derive from NonCryptographicHashAlgorithm, so any API that accepts that base accepts them.

What they're not. These are non-cryptographic - they are error-detection and distribution tools, not authentication tools. An attacker who can freely modify a message can trivially forge the digest. Pair them with a signature or a MAC if you need integrity against an adversary.

Pattern 2 - a keyed hash (SipHash)

SipHash was designed to keep hash tables safe from collision-DoS attacks. It is a reusable PRF - one secret key authenticates many messages, and even an adversary who knows the algorithm cannot produce collisions efficiently without the key.

using System.Security.Cryptography;
using Bodu.Security.Cryptography;

// 128-bit key (SipHash64.KeySize == 128) - generated once, stored in your process / vault.
byte[] key = new byte[SipHash64.KeySize / 8];   // 16 bytes
RandomNumberGenerator.Fill(key);

byte[] message = "the quick brown fox"u8.ToArray();

using var sip = new SipHash64 { Key = key };
byte[] digest = sip.ComputeHash(message);
ulong slotHash = BitConverter.ToUInt64(digest);

// Use the output as a stable 64-bit hash for routing / bucketing.

The key is exactly 16 bytes; a shorter or longer key is rejected at configuration time. SipHash128 gives you a 128-bit output for use cases that care about longer collision resistance. Both types expose CompressionRounds (default 2) and FinalizationRounds (default 4) if you need to trade speed for margin - the defaults are the standard SipHash-2-4 parameterization. See Using SipHash.

PRF vs one-time authenticator. SipHash is safe to reuse under one key. Poly1305 is not - its forgery bound holds only while the 32-byte key is used exactly once, which is why its CanReuseTransform is false. See Using Poly1305.

Pattern 3 - a cryptographic digest

Tiger is a classic 192-bit cryptographic hash optimized for 64-bit platforms. Use it for interoperability with existing Tiger-based systems, or when you want a digest in the same family as SHA-2 without pulling in a second dependency.

using Bodu.Security.Cryptography;

using var tiger = new Tiger();     // 192-bit default; set HashSize for 128/160
byte[] digest = tiger.ComputeHash(data);

For brand-new work, prefer the BCL's System.Security.Cryptography.SHA256 or System.Security.Cryptography.SHA512 - they are hardware-accelerated on most modern CPUs. The package also ships Whirlpool (512-bit, ISO/IEC 10118-3), CubeHash (tunable), Skein512 (Threefish-based, with a built-in MAC mode), and Blake2b / Blake3.

Warning

Snefru128 / Snefru256 are cryptographically broken - practical collisions are known. They ship for interoperability and research only; never use them to protect real data. See Using Snefru.

Pattern 3b - an extendable-output function (XOF)

When you need more output than a fixed digest supplies - arbitrary-length key material, deterministic randomness from a seed - reach for a XOF instead of a plain digest. Shake squeezes any positive multiple of 8 bits at a chosen 128- or 256-bit security level:

using Bodu.Security.Cryptography;

// SHAKE256, squeezing 1024 bits (128 bytes) from the seed.
using var shake = new Shake(outputBits: 1024, securityLevel: 256);
byte[] output = shake.ComputeHash(seed);   // 128 bytes

Unlike a Merkle-Damgård digest, a sponge XOF is immune to length extension and truncating its output is safe down to the security target. See Using SHAKE.

Pattern 4 - verifying a hash

Computing a hash is half the job; comparing it in constant time is the other half. The HashAlgorithmExtensions class (in Bodu.Security.Cryptography.Extensions) provides a VerifyHash helper that does the comparison with CryptographicOperations.FixedTimeEquals:

using Bodu.Security.Cryptography;
using Bodu.Security.Cryptography.Extensions;

byte[] expected = LoadExpectedDigestForFile("report.pdf");

using var hash = new Tiger();
bool match = hash.VerifyHash(new ReadOnlySpan<byte>(fileBytes), expected);

A direct SequenceEqual comparison leaks timing information to an observer and is unsafe for authentication decisions.

Pattern 5 - streaming hash over a file

For non-cryptographic streaming, NonCryptographicHashAlgorithm.Append(Stream) (or AppendAsync) consumes the stream without loading it into memory:

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

var hash = new Fnv1a64();
using (var stream = File.OpenRead("archive.bin"))
    hash.Append(stream);

byte[] fingerprint = hash.GetCurrentHash();

For a cryptographic digest the HashAlgorithm base exposes ComputeHash(Stream):

using System.Security.Cryptography;
using Bodu.Security.Cryptography;

using var tiger = new Tiger();
using var stream = File.OpenRead("archive.bin");
byte[] digest = tiger.ComputeHash(stream);

For a larger file where you want partial verifiability - "the first megabyte's digest is X; the second megabyte's digest is Y" - use a Merkle tree instead (next section).

Pattern 6 - Merkle trees

MerkleTree lets you compute a single root digest over a stream by hashing it in fixed-size blocks and folding the leaves into RFC 6962's Merkle Tree Hash. The root is the one any RFC 6962 implementation computes over the same blocks, so an inclusion proof from the same type can later prove the integrity of an individual chunk without rehashing the whole stream.

using System.Security.Cryptography;
using Bodu.Security.Cryptography;

var merkle = new MerkleTree(SHA256.Create);      // immutable; share it across threads

using var stream = File.OpenRead("archive.bin");
byte[] root = merkle.ComputeRootOfBlocks(stream, blockSize: 4096);

Each leaf is a SHA-256 of 0x00 and a 4 KiB block; each internal node is a SHA-256 of 0x01 and two concatenated child hashes; a lone node is promoted unchanged. The root changes if any byte of the input changes.

For large inputs where leaf hashing dominates, construct the tree with maxDegreeOfParallelism: -1 to hash leaves on several cores and fold them in order into the same tree; to build the root from the same write calls that feed a flat digest, use CreateBlockAccumulator - see Merkle trees and proofs.

Where to go next