northrook / hashes
Crockford Base32 hashing utilities for short, URL-safe identifiers, checksums, and time-sortable prefixes
Requires
- php: >=8.5
- ext-hash: *
- ext-mbstring: *
- ext-random: *
Requires (Dev)
- northrook/php-cs: dev-main
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^13
This package is auto-updated.
Last update: 2026-08-27 06:57:46 UTC
README
Crockford Base32 hashing utilities for short, URL-safe identifiers, checksums, and time-sortable prefixes.
Dual package: PHP (northrook/hashes) and TypeScript (@northrook/hashes).
Checksums
checksum is the low 60 bits of xxHash64 as 12 Crockford Base32 characters over encode(input) bytes (PHP Hash::encode() / TS Hash.encode()).
value / getHash() are different: they truncate xxHash64 of the raw input bytes.
encode() prefixes values with canonical type + length (no Unicode normalization), so checksum is a stable digest of the encoded form, not just the raw UTF-8 bytes.
No Unicode normalization is applied: NFC and NFD forms of the same character produce different digests.
Parity
Guaranteed cross-language parity is only for digests of shared input: PHP checksum / value (and native hash('xxh32'|'xxh64', …) in fixtures) ↔ TypeScript getChecksum / getHash / getXXH32 / getXXH64. For checksum, both sides hash encode(input) bytes; for value, both sides hash the raw input bytes. Locked by shared fixtures; not the TypeScript streaming XXHash32 / XXHash64 classes (PHP has no streaming hasher API — use hash()).
crypto / fast / time / ulid match style and API (Crockford alphabet, length bounds, method shapes, seed conventions) — not bit-identical outputs. RNG and clocks differ; JS also cannot mirror PHP’s int-vs-float time seed split exactly.
fixtures/digest-parity.json— golden vectors (seefixtures/README.md)- PHP:
vendor/bin/phpunit(ParityTest,DigestTest, …) - TypeScript:
npm run test:parity(plustest:xxhash/test:time/test:fast/test:crypto/test:ulid)
Regenerate goldens from PHP’s native xxHash after intentional digest-format changes:
php bin/generate-digest-parity.php
PHP
use Northrook\Hash; Hash::checksum('hello'); // 12-char Crockford (xxh64) Hash::value('hello', 6); // truncated Crockford Hash::time(); // time-sortable prefix (int ms / float seconds) Hash::ulid(); // time(10) + crypto(16) — 26-char ULID shape Hash::fast(); // mt_rand Crockford (best-effort session uniqueness) Hash::reset(); // clear fast() session map Hash::crypto(); // CSPRNG Crockford hash('xxh64', 'hello'); // raw hex — use native hash(), not Hash:: hash('xxh32', 'hello');
Requires ext-hash and ext-random, PHP >= 8.5.
fast uniqueness is best-effort (in-memory map + short retry budget), not absolute — tiny lengths can still warn-and-return a duplicate so runtime cannot loop. Call reset / resetFastHash from long-lived processes when the map should not grow unbounded.
TypeScript
Free functions plus a Hash facade mirroring the PHP method names:
import {getChecksum, Hash} from "@northrook/hashes"; getChecksum("hello"); Hash.checksum("hello"); Hash.xxh64("hello"); Hash.value("hello", 6); Hash.time(); // integer seed = ms; fractional = seconds Hash.ulid(); // time(10) + crypto(16) — 26-char ULID shape Hash.fast(); // Math.random Crockford (best-effort session uniqueness) Hash.reset(); // clear getFastHash session map
Browser / Electron oriented. Digests accept string (UTF-8) or Uint8Array.
XXHash32 / XXHash64 are TypeScript-only streaming hashers (used under getXXH32 / getXXH64). Prefer Uint8Array chunks when streaming: each string chunk is UTF-8-encoded independently, so splitting a surrogate pair across updates is not byte-equivalent to hashing the full string.
time / getTimeHash seed: integer → milliseconds; non-integer → seconds (floor(seed * 1000)), approximating PHP’s int/float split (style parity only — see Parity).