@systemix/hash

Password hashing and verification with Argon2id (the OWASP first choice), scrypt and PBKDF2, built entirely on Node.js built-ins — no native bindings. Completes the auth flow: generate → hash → store → verify.

Requires Node.js 24.7 or newer (crypto.argon2).

Installation

ts
pnpm add @systemix/hash

Basic Usage

ts
import { hashPassword, verifyPassword } from '@systemix/hash';

const encoded = await hashPassword('hunter2');
// → "$argon2id$v=19$m=19456,t=2,p=1$C5eX...$k9fT..."

await verifyPassword('hunter2', encoded); // → true
await verifyPassword('letmein', encoded); // → false

The output is a standard PHC string embedding the algorithm, parameters, salt, and hash — store it as a single database column. Hashes from 0.1.x (pbkdf2$…, scrypt$…) still verify.

Options

OptionTypeDefaultDescription
algorithm'argon2id' | 'scrypt' | 'pbkdf2''argon2id'Hashing algorithm.
memoryCostnumber19456Argon2id memory in KiB (max 262,144).
timeCostnumber2Argon2id passes (max 10).
parallelismnumber1Argon2id lanes (max 16).
costFactornumber131072scrypt cost factor N (power of 2).
blockSizeFactornumber8scrypt block size r.
parallelizationFactornumber1scrypt parallelization p.
digest'SHA-256' | 'SHA-384' | 'SHA-512''SHA-256'PBKDF2 digest function.
iterationsnumber600000PBKDF2 iteration count.
keyLengthnumber32Derived key length in bytes (max 1024).
saltUint8Arrayrandom 16BCustom salt (min 8 bytes).

Examples

Upgrade hashes at login

ts
import { verifyPassword, hashPassword, needsRehash } from '@systemix/hash';

if (await verifyPassword(password, user.passwordHash)) {
  if (needsRehash(user.passwordHash)) {
    user.passwordHash = await hashPassword(password);
    await user.save();
  }
}

needsRehash is true for legacy-format hashes, a different algorithm, or any weaker cost parameter.

Other algorithms

ts
await hashPassword('hunter2', { algorithm: 'scrypt' }); // N=2^17, r=8, p=1
await hashPassword('hunter2', { algorithm: 'pbkdf2' }); // 600,000 iterations (FIPS-140)

Low-level derivation

ts
import { argon2DeriveBits, generateSalt } from '@systemix/hash';

const key = await argon2DeriveBits('password', generateSalt(16), {
  memoryCost: 19_456,
  timeCost: 2,
  keyLength: 32,
});

Verification compares derived hashes in constant time. Cost parameters read from a stored hash are bounded, so a tampered hash cannot force unbounded CPU or memory use.

Try it

Open the Hash Playground demo to hash and verify passwords interactively.