@node-rs/argon2

argon2-rust binding for Node.js.
Argon2 is a key derivation function that was selected as the winner of the Password Hashing Competition(PHC) in July 2015.
Argon2 summarizes the state of the art in the design of memory-hard functions and can be used to hash passwords for credential storage, key derivation, or other applications.
It has a simple design aimed at the highest memory filling rate and effective use of multiple computing units, while still providing defense against tradeoff attacks (by exploiting the cache and memory organization of the recent processors).
Features
- Faster than node-argon2 and
node:crypto.argon2 at the same m/t/p (see Benchmarks).
- No node-gyp and postinstall.
- Cross-platform support, including Apple M1.
- Smaller file size after npm installation(476K vs node-argon2 3.7M).
@node-rs/argon2 supports all three algorithms:
- Argon2i: Optimizes against GPU cracking attacks but vulnerable to side-channels.
Accesses the memory array in a password dependent order, reducing the possibility of time–memory tradeoff (TMTO) attacks.
- Argon2d: Optimized to resist side-channel attacks.
Accesses the memory array in a password independent order, increasing the possibility of time-memory tradeoff (TMTO) attacks.
- Argon2id: default value, this is the default algorithm for normative recommendations.
Hybrid that mixes Argon2i and Argon2d passes.
Uses the Argon2i approach for the first half pass over memory and Argon2d approach for subsequent passes. This effectively places it in the “middle” between the other two: it doesn’t provide as good TMTO/GPU cracking resistance as Argon2d, nor as good of side-channel resistance as Argon2i, but overall provides the most well-rounded approach to both classes of attacks.
Benchmarks
Comparing each library's defaults is not 1:1. @node-rs/argon2 defaults to m=19456,t=2,p=1; argon2 (node-argon2) defaults to m=65536,t=3,p=4. See #841.
This bench pins the same password, salt, Argon2id v=19, m, t, p, and 32-byte tag. Only the raw KDF is timed. The tag is asserted equal on every run. Native impls are interleaved with each other; JS/wasm are a separate group so a 7 ms hash is not timed after a 400 ms JS loop.
Apple M5 Max / arm64 / Node 24 / argon2-rust 1.1.0. Median ms. See benchmark/.
Native async raw — same calling shape as node-argon2 (no sync API there):
| m=19456 KiB, t=2, p=1 | 7.58 | 15.41 | 13.05 |
| m=65536 KiB, t=3, p=1 | 49.35 | 84.57 | 71.71 |
| m=65536 KiB, t=3, p=4 | 13.62 | 22.54 | 20.83 |
JS / wasm raw — same params, same tags:
| m=19456 KiB, t=2, p=1 | 19.79 | 86.14 |
| m=65536 KiB, t=3, p=1 | 104.09 | 433.07 |
| m=65536 KiB, t=3, p=4 | 106.06 | 435.39 |
hash-wasm and @noble/hashes do not run lanes in parallel, so their p=4 row is the p=1 work.
API
export const enum Algorithm {
Argon2d = 0,
Argon2i = 1,
Argon2id = 2,
}
export const enum Version {
V0x10 = 0,
V0x13 = 1,
}
export interface Options {
memoryCost?: number | undefined | null
timeCost?: number | undefined | null
outputLen?: number | undefined | null
parallelism?: number | undefined | null
algorithm?: Algorithm | undefined | null
version?: Version | undefined | null
secret?: Buffer | undefined | null
}
export function hash(
password: string | Buffer,
options?: Options | undefined | null,
abortSignal?: AbortSignal | undefined | null,
): Promise<string>
export function verify(
hashed: string | Buffer,
password: string | Buffer,
options?: Options | undefined | null,
abortSignal?: AbortSignal | undefined | null,
): Promise<boolean>
parseOptions — needs-rehash checks
parseOptions parses an encoded PHC hash string and returns the parameters it was created with. Compare them against your current policy to decide whether a stored hash should be rehashed at next login.
import { Algorithm, parseOptions, Version } from '@node-rs/argon2'
const POLICY = {
algorithm: Algorithm.Argon2id,
version: Version.V0x13,
memoryCost: 19456,
timeCost: 2,
parallelism: 1,
outputLen: 32,
} as const
function needsRehash(hashed: string): boolean {
const parsed = parseOptions(hashed)
return (
parsed.algorithm !== POLICY.algorithm ||
parsed.version !== POLICY.version ||
parsed.memoryCost !== POLICY.memoryCost ||
parsed.timeCost !== POLICY.timeCost ||
parsed.parallelism !== POLICY.parallelism ||
parsed.outputLen !== POLICY.outputLen ||
parsed.saltLen < 16
)
}
export interface ParsedHashOptions {
algorithm: Algorithm
version: Version
memoryCost: number
timeCost: number
parallelism: number
outputLen: number
saltLen: number
}
export function parseOptions(hashed: string | Uint8Array): ParsedHashOptions