Algorithms

Argon2id

The Argon2 that RFC 9106 tells you to pick. Memory-hard with a salt it draws and prints when you give none
IDargon2id36 / 38Argon2 · variable
Hash / Password

Argon2id

The Argon2 that RFC 9106 tells you to pick.

Digest
keyLength you pick
HMAC
no key mode
Security
The primary variant of RFC 9106
Family
3 in Argon2
8 options, 0 required

Sample

hex
f5122bfd86ada0736205873b5d95a42849fc4ee7880900a1995dc8c12314dcd0
base64
9RIr/YatoHNiBYc7XZWkKEn8TueICQChmV3IwSMU3NA=

Options

encodingstring
default hex
Output encoding: hex, base64, base64url, binary
saltstring
optional
Salt in hex; 32 random bytes when omitted
memorynumber
default 65536
Memory in KiB, at least 8 per lane
iterationsnumber
default 3
Passes over the memory
parallelismnumber
default 4
Lanes
keyLengthnumber
default 32
Output key length in bytes, at least 4
secretstring
optional
Secret key in hex, a pepper
associatedDatastring
optional
Associated data in hex

Access

Createcreate("argon2id")
CLIhashes argon2id 'hello world' --salt 73616c7473616c74 --memory 1024 --iterations 2 --parallelism 1
Tryplayground with the sample above
Kinargon2i, argon2d

Three Argon2 variants, one engine, and a letter that decides how paranoid it gets. RFC 9106 calls argon2id the primary one. It reads memory in a fixed order for the first half of the first pass, like argon2i. After that the reads follow the data, like argon2d. A side channel learns little. A GPU still pays for every KiB.

ts
create("argon2id").hash("password", {
  salt: "73616c7473616c74",
  memory: 1024,
  iterations: 2,
  parallelism: 1,
}).digest;
// "820ecc0dff8ed2ee188788d8bd38d7269b3a7118c1210bc9e2becc40ebf5039f"

That salt is saltsalt in hex. Why not plain salt? Argon2 wants at least 8 bytes, so four get an InvalidOptionError. Leave the salt out and 32 random bytes come back in result.options. Salted KDFs says why you keep them.

memory counts KiB, not bytes. The defaults are RFC 9106's second recommended option: 64 MiB, 3 passes, 4 lanes, 32 bytes out. Its first option wants 2 GiB. Fine on a server, rude in a browser tab. So the page above runs its sample at 1 MiB.

Lanes change the result, not the speed. This is plain TypeScript on one thread, and the lanes run one after another. Matching a hash from somewhere else? Copy all four numbers, or you get a perfectly valid wrong digest.

secret and associatedData take hex. They're K and X in the RFC, mixed into the first hash. A pepper goes in secret. associatedData comes back in result.options, the secret doesn't. You already have it, and a log doesn't need it.

Need the bytes for another library? @agntn/hashes/argon2 has all three variants as plain functions:

ts
import { argon2id } from "@agntn/hashes/argon2";

const text = new TextEncoder();
argon2id(text.encode("password"), text.encode("saltsalt"), {
  memory: 1024,
  iterations: 2,
  parallelism: 1,
  keyLength: 32,
});
// the same 32 bytes as above

No defaults and no random salt here. You pass the four numbers, and secret and associatedData as bytes if you need them. Anything RFC 9106 rules out throws before a single block is allocated. Asking for 2 GiB? It'll try. Only the tools cap memory.