---
title: "Hashing"
description: "Pick an algorithm by name and hash text or bytes. Hex and base64 and base64url or raw bytes out"
canonical_url: "https://hashes.agntn.dev/guide/hashing"
---
# Hashing

> Pick an algorithm by name and hash text or bytes. Hex and base64 and base64url or raw bytes out

## One method

```ts
import { create } from "@agntn/hashes";

const sha256 = create("sha256");

sha256.hash("abc").digest;
// "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
sha256.hash("abc", { encoding: "base64" }).digest;
// "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0="
sha256.hash("abc", { encoding: "base64url" }).digest;
// "ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0"
sha256.hash("abc", { encoding: "binary" }).digest;
// Uint8Array(32)
```

That's the whole API for most people. `hash(input, options)` on any algorithm, the same result shape back. `hex` is the default. `base64url` drops the padding, the way JWTs write it. `binary` hands you the raw `Uint8Array`, which is what you want before you feed the digest into another hash.

Those four are the whole list. Anything else throws an `InvalidOptionError`. TypeScript stops `"HEX"` at compile time, and plain JavaScript gets the error at runtime. A loud error beats a base64 digest wearing a hex label.

## Text or bytes

A string is read as UTF-8. A `Uint8Array` is hashed as it is. The difference matters the moment your input is already bytes written as hex, like a public key:

```ts
const key = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";

create("hash160").hash(key).digest;
// "b170e26bb62fb9dbfa28bb2f5e320c96459a2bed", the 66 characters, hashed as text
create("hash160").hash(Uint8Array.fromHex(key)).digest;
// "751e76e8199196d454941c45d1b3a323f1433bd6", the 33 bytes, which is what Bitcoin hashes
```

The first one is a perfectly valid digest of a string nobody meant. That's the classic bug, and it's silent. The library takes bytes, so pass bytes. The CLI and the agent tools can't pass a `Uint8Array`, so they take `--input-encoding hex` and `inputEncoding: "hex"` instead and decode it for you. See [CLI](https://hashes.agntn.dev/guide/cli) and [Agents](https://hashes.agntn.dev/guide/agents).

Non-ASCII text is fine, UTF-8 is UTF-8. `"żółw"` and `new TextEncoder().encode("żółw")` give the same digest.

## Many rounds

"SHA-512 of the answer, ten thousand times." Puzzle pages love that one. `rounds` runs the loop for you:

```ts
const sha256 = create("sha256");

sha256.hash("abc", { rounds: 2 }).digest;
// "4f8b42c22dd3729b519ba6f68d2da7cc5b2d606d05daed5ad5128cc03e6c6358"
sha256.hash("abc", { rounds: 2, chain: "hex" }).digest;
// "dfe7a23fefeea519e9bbfdd1a6be94c4b2e4529dd6b7cbea83f9959c2621b13c"
```

Same input, same count, two answers. Why? Round two has to hash something. `chain: "bytes"` is the default and hashes the raw digest. `chain: "hex"` hashes the lowercase text that spells it. Both recipes are out there. A page rarely says which one it means, so try both before you doubt your answer.

Does the first digest look familiar? It's `hash256`. Bitcoin has run two rounds of SHA-256 over bytes since 2009.

Past one round, `options` in the result names `rounds` and `chain`. One logged result is enough to repeat it. Every fixed-length digest takes them. HMAC and the KDFs don't, since they have their own cost. More rounds there is an error, not a quiet single pass.

## A pile of inputs

Twelve words, twelve digests? Don't write the loop. `hash` eats a list too. The options go once:

```ts
create("sha256").hash(["hello", "world"]).map((result) => result.digest);
// ["2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
//  "486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7"]
```

A string gets one result. A list gets an array, same order. Underneath it's a `map`, nothing clever. But every algorithm does it, your [own](https://hashes.agntn.dev/guide/custom) `FixedHash` or `Kdf` included. A KDF without a salt draws a fresh one per input. Two users with the same password still get two different hashes. Isn't that the whole point of a salt?

## Bytes in, bytes out

Building a wallet or an address codec? Then you don't want a result object. You have bytes, you want bytes, and you'd rather not ship every algorithm here to hash with one.

```ts
import { Sha512Hasher, blake2b, hash160, hmac, pbkdf2 } from "@agntn/hashes";

const key = Uint8Array.fromHex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798");
hash160(key).toHex(); // "751e76e8199196d454941c45d1b3a323f1433bd6"
blake2b(key, 20); // Uint8Array(20), any length from 1 to 64

// The BIP39 test vector, then its BIP32 master key
const text = new TextEncoder();
const sha512 = () => new Sha512Hasher();
const words = "abandon ".repeat(11) + "about";
const seed = pbkdf2(sha512, text.encode(words), text.encode("mnemonic"), 2048, 64);
// "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc1..."
hmac(sha512, text.encode("Bitcoin seed"), seed);
// Uint8Array(64), the key "1837c1be8e2995ec..." and then the chain code
```

Each one takes a `Uint8Array` and gives you a new one. Here's the lineup:

- Digests: `sha224`, `sha256`, `sha384`, `sha512`, `ripemd160`, `hash160`, `hash256`, `keccak256`, `sha3_256`, `sha3_512`, `blake256` and `blake2b`.
- Checksums: `crc32`, `crc64`, `crc24`, `crc16Xmodem`, `adler32` and `xxhash`.
- Old formats that still ask for them: `sha1` and `md5`.

`blake2b` also takes a personalization of up to 16 bytes as its third argument, because Zcash's F4Jumble tags every call with one. The checksums take their variant the same way the registry does, as an argument: `crc32(bytes, "bzip2")`, `xxhash(bytes, 32, seed)`.

Got a message that stops inside a byte? `sha256Bits(bytes, 7)` hashes only the first 7 bits, packed from the top. [SHA-256](https://hashes.agntn.dev/algorithms/sha256) has the details.

`hmac`, `pbkdf2` and `hkdf` take a function that makes a hasher: `Sha256Hasher`, `Sha512Hasher`, `Ripemd160Hasher`, `Blake2bHasher`, `Sha1Hasher` or `Md5Hasher`. So do `hkdfExtract` and `hkdfExpand`, the two halves of `hkdf`, for a protocol that calls only one. And so does `evpBytesToKey`, for whatever `openssl enc` and CryptoJS locked with a passphrase.

`scrypt` skips the hasher, since RFC 7914 fixes it to SHA-256. `argon2id`, `argon2i` and `argon2d` skip it too and take their cost as an object, `{ memory, iterations, parallelism, keyLength }`. `bcrypt` takes a 16-byte salt and a cost, and `bcryptString` turns its 23 bytes into the `$2b$` string.

Need SHA-3, SHA-224 or SHA-384 under `hmac` or `pbkdf2`? `Sha3_256Hasher`, `Sha3_512Hasher`, `Keccak256Hasher`, `Sha224Hasher` and `Sha384Hasher` plug right in.

A hasher works alone too. Feed it with `update`, finish with `digest`. OpenPGP's S2K pushes up to 65 MB through one hash. Gluing that into one buffer first would be silly. So would `create("sha224").hasher()` in a bundle. It works, but it drags the whole registry along. About 100 kB for a hash that fits in 4 kB.

What if the sponge itself is in the way? STROBE pokes the raw Keccak state byte by byte. Merlin runs on STROBE, and sr25519 runs on Merlin. So `keccakF1600` hands you the permutation bare. Give it 200 bytes, lanes little-endian. It permutes them in place. Same rounds SHA-3 runs, so nobody carries a second Keccak.

Import one and the registry stays out of your bundle. Pass a string and you get a `HashError`. A hex string hashed as text is exactly the bug from the section above, so here it's not an option at all.

That's a bundler talking. Plain Node doesn't tree-shake, so `import { sha1 } from "@agntn/hashes"` still parses every algorithm in the package, and a consumer that needs one digest pays for them on every start. Each file of byte functions has its own entry for that:

```ts
import { sha1 } from "@agntn/hashes/sha1";
import { Sha256Hasher } from "@agntn/hashes/sha2";
import { hmac } from "@agntn/hashes/hmac";
```

<table>
<thead>
  <tr>
    <th>
      Entry
    </th>
    
    <th>
      Exports
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        @agntn/hashes/sha1
      </code>
    </td>
    
    <td>
      <code>
        sha1
      </code>
      
      , <code>
        Sha1Hasher
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/md5
      </code>
    </td>
    
    <td>
      <code>
        md5
      </code>
      
      , <code>
        Md5Hasher
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/sha2
      </code>
    </td>
    
    <td>
      <code>
        sha224
      </code>
      
      , <code>
        sha256
      </code>
      
      , <code>
        sha256Bits
      </code>
      
      , <code>
        sha384
      </code>
      
      , <code>
        sha512
      </code>
      
      , <code>
        hash256
      </code>
      
      , <code>
        Sha224Hasher
      </code>
      
      , <code>
        Sha256Hasher
      </code>
      
      , <code>
        Sha384Hasher
      </code>
      
      , <code>
        Sha512Hasher
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/ripemd160
      </code>
    </td>
    
    <td>
      <code>
        ripemd160
      </code>
      
      , <code>
        hash160
      </code>
      
      , <code>
        Ripemd160Hasher
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/keccak
      </code>
    </td>
    
    <td>
      <code>
        keccak256
      </code>
      
      , <code>
        sha3_256
      </code>
      
      , <code>
        sha3_512
      </code>
      
      , <code>
        keccakF1600
      </code>
      
      , <code>
        Keccak256Hasher
      </code>
      
      , <code>
        Sha3_256Hasher
      </code>
      
      , <code>
        Sha3_512Hasher
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/blake2b
      </code>
    </td>
    
    <td>
      <code>
        blake2b
      </code>
      
      , <code>
        Blake2bHasher
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/blake256
      </code>
    </td>
    
    <td>
      <code>
        blake256
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/crc
      </code>
    </td>
    
    <td>
      <code>
        crc32
      </code>
      
      , <code>
        crc64
      </code>
      
      , <code>
        crc24
      </code>
      
      , <code>
        crc16Xmodem
      </code>
      
      , <code>
        CRC32_VARIANTS
      </code>
      
      , <code>
        CRC64_VARIANTS
      </code>
      
      , <code>
        CRC24_VARIANTS
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/adler32
      </code>
    </td>
    
    <td>
      <code>
        adler32
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/xxhash
      </code>
    </td>
    
    <td>
      <code>
        xxhash
      </code>
      
      , <code>
        XXHASH_BITS
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/hmac
      </code>
    </td>
    
    <td>
      <code>
        hmac
      </code>
      
      , <code>
        pbkdf2
      </code>
      
      , <code>
        hkdf
      </code>
      
      , <code>
        hkdfExtract
      </code>
      
      , <code>
        hkdfExpand
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/evp
      </code>
    </td>
    
    <td>
      <code>
        evpBytesToKey
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/scrypt
      </code>
    </td>
    
    <td>
      <code>
        scrypt
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/argon2
      </code>
    </td>
    
    <td>
      <code>
        argon2id
      </code>
      
      , <code>
        argon2i
      </code>
      
      , <code>
        argon2d
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @agntn/hashes/bcrypt
      </code>
    </td>
    
    <td>
      <code>
        bcrypt
      </code>
      
      , <code>
        bcryptString
      </code>
    </td>
  </tr>
</tbody>
</table>

They're the same functions the root exports, not copies. On Node 26 `sha1` from its own entry loads about three times faster than from the root.

## Names

`create()` is strict: the exact registry key or an `UnknownAlgorithmError`. For names a human typed, use `resolveAlgorithm()`. It ignores case and punctuation and looks the result up among the keys and the labels, nothing smarter. A name that is a key wins. Otherwise, if two algorithms spell the same that way, it takes neither.

```ts
import { algorithms, has, resolveAlgorithm } from "@agntn/hashes";

resolveAlgorithm("SHA3_256").name(); // "sha3-256"
resolveAlgorithm("Blake2b 256").name(); // "blake2b-256"
resolveAlgorithm("RIPEMD-160").name(); // "ripemd160"
has("sha3-384"); // false, unless you registered one
algorithms(); // every key, built-ins in listing order, then yours
```

No fuzzy match on purpose. `sha3` could mean four different things, and guessing is how you get a wrong digest that looks right.

It won't guess, but it does point. Half a name gets the keys it begins, and a variant written into the name gets the option that takes it:

```ts
resolveAlgorithm("sha3"); // UnknownAlgorithmError: ... Did you mean sha3-256 or sha3-512?
resolveAlgorithm("CRC-32/BZIP2"); // ... Did you mean crc32 with variant=bzip2?
resolveAlgorithm("tiger2"); // ... Available: sha256, sha384, ...
```

Why no hint for `tiger2`? Tiger is one letter away, and Tiger2 pads differently, so that hint would hand you the wrong digest with a straight face. `error.suggestions` carries the same hints for code.

## What an algorithm says about itself

`info()` is how an algorithm describes itself, and the site, the CLI and the tools all read it:

```ts
create("keccak256").info();
// {
//   name: "keccak256",
//   label: "Keccak-256",
//   family: "Keccak",
//   category: "cryptographic",
//   digestLength: 32,
//   hmac: true,
//   options: [{ name: "encoding", ... }, { name: "key", ... }],
//   securityNote: "128-bit collision resistance, ...",
//   ...
// }
```

`options` lists everything the algorithm takes, with type, default and description. A KDF declares `salt` and its costs there, xxHash its `seed`. The CLI turns them into flags and the tools take them as `parameters`, so an option you add in one place reaches all of them.

## Families and categories

Two different questions. The family is where an algorithm comes from: SHA, Keccak, BLAKE, RIPEMD, MD, CRC, xxHash, FNV, scrypt, PBKDF, HKDF, OpenSSL, Argon2. SHA-0 and SHA3-256 are both SHA, and they have nothing else in common.

The category is what it's fit for. Four of them, and they mean what they say:

- **cryptographic.** SHA-2, SHA-3, Keccak, BLAKE, RIPEMD-160, the Bitcoin compositions and HKDF. Use these when it matters.
- **legacy.** MD5, SHA-1 and SHA-0. Broken for collisions, the first two still in every checksum file and inside Git. Fine for matching a checksum somebody else published. Not fine for anything you'd sign.
- **non-cryptographic.** CRC-32, CRC-16/XMODEM, xxHash, FNV-1a. Fast, tiny, trivial to collide on purpose. Tables, dedup, error detection.
- **password.** scrypt, PBKDF2 and the three Argon2 variants. Slow on purpose, and they need a salt. They have [their own page](https://hashes.agntn.dev/guide/kdf). [EVP_BytesToKey](https://hashes.agntn.dev/algorithms/evp-bytestokey) sits here too, fast and weak. It opens what `openssl enc` and CryptoJS locked.

`hashes algorithms -c legacy` lists one category, `-f sha` one family, and both together narrow it down to SHA-1 and SHA-0. `hashes_algorithms` takes `category` and `family` the same way. Case doesn't matter for the family.


## Sitemap

See the full [sitemap](https://hashes.agntn.dev/sitemap.md) for all pages.
