Classic string hashes
A collection of the classic "one-liner" hash functions that appear in textbooks, compilers, and early web servers. They all produce a 32-bit or 64-bit output, none of them takes a key, and their public API is identical - configure any optional seed, call Append to feed data, and call GetCurrentHash to read the digest.
None of these are cryptographic. They are in the package for interoperability with legacy systems, for teaching purposes, and for use inside a trust boundary where collision-DoS is not a concern. For anything adversary-facing, use SipHash64; see the cryptography hashing guide.
The family at a glance
| Type | Width | Seed / configuration | Origin |
|---|---|---|---|
| Bernstein | 32 bits | InitialValue (default 5381), UseModifiedAlgorithm (xor vs add) |
Daniel J. Bernstein's "djb2", posted to comp.lang.c. |
| BKDR | 32 bits | Seed - one of a published set of odd multipliers |
Kernighan & Ritchie, The C Programming Language. |
| SDBM | 32 bits | None | The SDBM public-domain database. |
| JSHash | 32 bits | None (seed 0x4E67C6A7) |
Justin Sobel's JavaScript-origin hash. |
| Elf64 | 64 bits | Seed (default 0) |
The ELF symbol-table hash, widened to 64 bits. |
| ApHash | 32 bits | None (seed 0xAAAAAAAA) |
Arash Partow's hash. |
| Pjw32 | 32 bits | None | Peter Weinberger's PJW hash (AT&T compiler). |
| SuperFastHash | 32 bits | None | Paul Hsieh's SuperFastHash; tuned for short keys. |
All eight derive from NonCryptographicHashAlgorithm.
Pattern 1 - a default-configured hash
Every type has a parameterless constructor that uses the historically canonical parameters for that function:
using System.Text;
using Bodu.IO.Hashing;
byte[] data = Encoding.UTF8.GetBytes("the quick brown fox");
var hash = new Bernstein(); // djb2 - seed 5381, XOR form
hash.Append(data);
byte[] digest = hash.GetCurrentHash();
uint h = BitConverter.ToUInt32(digest);
Swap Bernstein for any of the others - the API is the same.
Pattern 2 - Bernstein (djb2), add vs XOR
Bernstein's original posting used addition (h = h * 33 + c). The XOR form (h = h * 33 ^ c) distributes slightly better on ASCII input and is what most later ports use. Bernstein exposes both through UseModifiedAlgorithm.
using Bodu.IO.Hashing;
// Original: h = (h * 33) + c
var original = new Bernstein(Bernstein.DefaultInitialValue, useModifiedAlgorithm: false);
// "djb2a": h = (h * 33) ^ c - the common modern variant
var modified = new Bernstein(Bernstein.DefaultInitialValue, useModifiedAlgorithm: true);
// Or set the properties before the first Append
var alt = new Bernstein { InitialValue = 0, UseModifiedAlgorithm = true };
Both properties are only settable before the first Append - changing them mid-stream would invalidate the running state, so the setters throw once input has been fed.
Pattern 3 - BKDR's published multiplier set
BKDR is a family: the multiplier is a repeating-digit odd number from the published set (31, 131, 1313, 13131, 131313, 1313131, 13131313, 131313131, 1313131313). The default is BKDR.DefaultSeed (131):
using Bodu.IO.Hashing;
var bkdr = new BKDR(seed: 1313); // must be one of the published values
bkdr.Append(Encoding.UTF8.GetBytes("example"));
byte[] digest = bkdr.GetCurrentHash();
Passing a value outside the published set throws - the seed is a property of the published "standard BKDR family", not an arbitrary multiplier.
Pattern 4 - Elf64 with a custom seed
Elf64 widens the classic ELF symbol-table hash to 64 bits and exposes a seed so you can salt it for separate hash-table lanes:
using Bodu.IO.Hashing;
var elf = new Elf64(seed: 0xDEADBEEFUL);
elf.Append(Encoding.UTF8.GetBytes("/usr/bin/ls"));
byte[] digest = elf.GetCurrentHash();
The seed is not a key - it does not provide adversarial resistance. It only re-origins the accumulator so two parallel tables get independent distributions.
Pattern 5 - Append / GetCurrentHash / Reset
Every type in this family behaves identically under the NonCryptographicHashAlgorithm contract:
using Bodu.IO.Hashing;
var hash = new SDBM();
hash.Append(header);
hash.Append(body);
byte[] partial = hash.GetCurrentHash(); // snapshot, non-destructive
hash.Append(trailer);
byte[] full = hash.GetCurrentHash();
hash.Reset(); // back to the configured seed / initial state
Picking a function
- Hashing identifiers inside a compiler or symbol table?
Pjw32orElf64- these are the functions you'll meet in the original source. - Quick hash-table function in throwaway code?
BernsteinwithUseModifiedAlgorithm = trueis simple, well-known, and distributes reasonably. - Hashing many short keys?
SuperFastHashis tuned for short inputs - but it buffers its input, so avoid it for very large streams (use a block-based fingerprint instead). - Hashing user-controlled input? None of these - reach for SipHash64.
- Reproducing a known published digest from another tool? Match on algorithm, width, seed, and (for Bernstein) the add-vs-XOR variant.
For general-purpose fingerprinting where quality and speed both matter, CityHash64 and Fnv1a64 outperform everything in this family - see the CityHash and FNV guides.
Where to go next
- Using FNV, Using CityHash - modern non-cryptographic hashes with better distribution.
- Using Pearson - table-driven classic hash with configurable output width.
- Cryptography hashing guide - when a classic hash is not enough.
- Bodu.IO.Hashing namespace page - key types and design notes.
- Hashing & Cryptography guides - every guide in this topic, across Bodu.IO.Hashing and Bodu.Security.Cryptography.