Getting Started
Why this exists
Ask a model for the SHA-256 of a string. It gives you 64 hex characters, very confidently, and they're wrong. Hashing is the one thing a language model can't fake, so it should call something that actually hashes. @agntn/hashes is that something, and it's the same thing in your TypeScript and in your terminal.
It also has the hashes chains quietly depend on. Keccak-256 with its pre-SHA-3 padding, HASH160, double SHA-256, BLAKE2b cut to 32 or 28 bytes, SHA-512Half. Those are the ones people get subtly wrong, and subtly wrong means a different address.
No HTTP, no keys, no telemetry, no hashing dependency. Every digest is plain TypeScript in src/core/, and the library imports nothing from node:*. That's why this site can run it in your browser.
Install
pnpm add @agntn/hashes
Node.js 26 or newer. It needs Uint8Array with native hex and base64, and 26 is where that stopped being a question.
First call
import { create } from "@agntn/hashes";
const result = create("sha256").hash("abc");
result.digest; // "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
result.digestLength; // 32
create(name) wants the exact registry key and hands back one cached instance. resolveAlgorithm("SHA-256") forgives case and punctuation and takes the label too, then wants an exact match. No prefixes, no fuzzy guess. A typo is an UnknownAlgorithmError, and its message lists every name that does exist. Half a name gets only what it could have meant: sha3 names sha3-256 and sha3-512.
The shape
Every hash() returns this, whatever the algorithm:
interface HashResult {
digest: string | Uint8Array; // bytes only with encoding "binary"
algorithm: string; // "sha256"
operation: "hash" | "hmac";
encoding: "hex" | "base64" | "base64url" | "binary";
digestLength: number; // bytes of the raw digest
options: Record<string, unknown>; // what the digest depends on: encoding, salt, cost, seed
}
options is the part people skip and then regret. For scrypt it holds the salt it drew and the cost it ran with, and without those the digest is a string that looks random and that you can never reproduce.
What ships
| Options | Security | ||||
|---|---|---|---|---|---|
| SHA-256sha256 | Cryptographic | 256-bit | HMAC yes | no options | 128-bit collision resistance |
| SHA-384sha384 | Cryptographic | 384-bit | HMAC yes | no options | 192-bit collision resistance |
| SHA-512sha512 | Cryptographic | 512-bit | HMAC yes | no options | 256-bit collision resistance |
| SHA-224sha224 | Cryptographic | 224-bit | HMAC yes | no options | 112-bit collision resistance |
| SHA-512/224sha512-224 | Cryptographic | 224-bit | HMAC yes | no options | 112-bit collision resistance |
| SHA-512/256sha512-256 | Cryptographic | 256-bit | HMAC yes | no options | 128-bit collision resistance |
| SHA-512Halfsha512-half | Cryptographic | 256-bit | HMAC no | no options | 128-bit collision resistance |
| SHA3-256sha3-256 | Cryptographic | 256-bit | HMAC yes | no options | 128-bit collision resistance |
| SHA3-512sha3-512 | Cryptographic | 512-bit | HMAC yes | no options | 256-bit collision resistance |
| Keccak-256keccak256 | Cryptographic | 256-bit | HMAC yes | no options | 128-bit collision resistance |
| BLAKE2bblake2b | Cryptographic | 512-bit | HMAC yes | no options | 256-bit collision resistance |
| BLAKE2b-256blake2b-256 | Cryptographic | 256-bit | HMAC no | no options | 128-bit collision resistance |
| BLAKE2b-224blake2b-224 | Cryptographic | 224-bit | HMAC no | no options | 112-bit collision resistance |
| BLAKE2sblake2s | Cryptographic | 256-bit | HMAC yes | no options | 128-bit collision resistance |
| BLAKE3blake3 | Cryptographic | 256-bit | HMAC no | no options | 128-bit security against collisions and preimages |
| BLAKE-256blake256 | Cryptographic | 256-bit | HMAC no | no options | 128-bit collision resistance |
| RIPEMD-160ripemd160 | Cryptographic | 160-bit | HMAC yes | no options | 80-bit collision resistance |
| RIPEMD-320ripemd320 | Cryptographic | 320-bit | HMAC yes | no options | No stronger than RIPEMD-160: the wider digest adds no collision resistance |
| HASH160hash160 | Cryptographic | 160-bit | HMAC no | no options | 80-bit collision resistance |
| HASH256 (double SHA-256)hash256 | Cryptographic | 256-bit | HMAC no | no options | 128-bit collision resistance |
| Whirlpoolwhirlpool | Cryptographic | 512-bit | HMAC yes | no options | No attack on the full hash |
| MD5md5 | Legacy | 128-bit | HMAC yes | no options | BROKEN: collision attacks known since 2004 |
| MD4md4 | Legacy | 128-bit | HMAC yes | no options | BROKEN: practical collisions since 1996 |
| MD2md2 | Legacy | 128-bit | HMAC yes | no options | BROKEN: preimages in 2^73 and collisions in 2^63.3 (RFC 6149) |
| NTLMntlm | Legacy | 128-bit | HMAC no | no options | BROKEN as a password hash: no salt and no cost |
| SHA-1sha1 | Legacy | 160-bit | HMAC yes | no options | BROKEN: practical collision attack (SHAttered) |
| SHA-0sha0 | Legacy | 160-bit | HMAC yes | no options | BROKEN: full collision found in 2004 |
| RIPEMD-128ripemd128 | Legacy | 128-bit | HMAC yes | no options | 64-bit collision resistance |
| RIPEMD-256ripemd256 | Legacy | 256-bit | HMAC yes | no options | No stronger than RIPEMD-128: the wider digest adds no collision resistance |
| HAVALhaval | Legacy | 128-bit | HMAC yes | passes?, bits? | BROKEN: practical collisions with 3 passes (2^29 |
| Tigertiger | Legacy | 192-bit | HMAC yes | no options | No practical attack on all 24 rounds |
| CRC-32crc32 | Non-cryptographic | 32-bit | HMAC no | variant? | NOT for security: error-detection checksum only |
| CRC-64crc64 | Non-cryptographic | 64-bit | HMAC no | variant? | NOT for security: error-detection checksum only |
| CRC-24crc24 | Non-cryptographic | 24-bit | HMAC no | variant? | NOT for security: error-detection checksum only |
| CRC-16/XMODEMcrc16-xmodem | Non-cryptographic | 16-bit | HMAC no | no options | NOT for security |
| Adler-32adler32 | Non-cryptographic | 32-bit | HMAC no | no options | NOT for security: weaker than CRC-32 on short inputs |
| xxHashxxhash | Non-cryptographic | 64-bit | HMAC no | bits?, seed? | NOT for security: fast hash for hash tables |
| FNV-1a (64-bit)fnv1a | Non-cryptographic | 64-bit | HMAC no | no options | NOT for security: simple hash for hash tables |
| scryptscrypt | Password | variable | HMAC no | salt?, N?, r?, p?, keyLength? | Memory-hard KDF |
| PBKDF2pbkdf2 | Password | variable | HMAC no | salt?, iterations?, digest?, keyLength? | OWASP Password Storage Cheat Sheet: >=600000 iterations with HMAC-SHA256 |
| HKDFhkdf | Cryptographic | variable | HMAC no | salt?, info?, digest?, keyLength? | Not for passwords: HKDF adds no cost |
| EVP_BytesToKeyevp-bytestokey | Password | variable | HMAC no | salt?, digest?, iterations?, keyLength?, ivLength? | Weak: one fast hash per block |
| Argon2idargon2id | Password | variable | HMAC no | salt?, memory?, iterations?, parallelism?, keyLength?, secret?, associatedData? | The primary variant of RFC 9106 |
| Argon2iargon2i | Password | variable | HMAC no | salt?, memory?, iterations?, parallelism?, keyLength?, secret?, associatedData? | Its reads leak nothing to a side channel |
| Argon2dargon2d | Password | variable | HMAC no | salt?, memory?, iterations?, parallelism?, keyLength?, secret?, associatedData? | Its reads follow the data and can leak through a side channel |
| bcryptbcrypt | Password | 184-bit | HMAC no | salt?, cost? | A password past 72 bytes is refused |
Every row comes from create(name).info(), read in your browser. The HMAC column says which ones take a key. The rest refuse it with an error, they don't quietly hash without it.
Errors
import { HashError, InvalidOptionError, UnknownAlgorithmError, create } from "@agntn/hashes";
try {
create("scrypt").hash("password", { N: 1000 });
} catch (error) {
if (error instanceof InvalidOptionError) {
error.option; // "N"
error.reason; // "must be a power of 2 and >= 2"
}
}
A name nobody registered is an UnknownAlgorithmError. A bad value is an InvalidOptionError with the option, the value and the reason. A required one left out is a MissingOptionError. Anything else an algorithm throws gets wrapped by normalizeError into a HashError that names the algorithm. The CLI prints the message on one line and exits 1. No stack trace, the input was wrong, not you.
Next
- Hashing: encodings, text versus bytes, names.
- HMAC and verify: keys, and comparing digests without a timing leak.
- Salted KDFs: scrypt and PBKDF2, and why they print the salt.
- Length extension: sign a longer message without the secret.
- Identify a hash: which algorithms a digest may come from.
- Search for a transform: which words, joined and hashed how, give a digest.
- CLI: the
hashescommand. - Agents: the same tools over MCP, local or remote, Pi, OMP and the AI SDK.
- Custom algorithms: extend
FixedHash, register it. - Playground: every algorithm, in the page.