hash-of-lyra
v0.2.0
Published
> This next hash is dedicated to the world!
Maintainers
Readme
hash-of-lyra
This next hash is dedicated to the world!
Hash of Lyra is an (almost) zero-dependency JavaScript password hashing library.
Features
- Works in various JavaScript environments
- ✅ Node.js LTS (20, 22, 24)
- ✅ Bun
- planned Cloudflare Workers (with/without
node_compat)
- Supports various algorithms (subject to environment)
- Argon2 (
argon2id,argon2i,argon2d) - Scrypt (
scrypt) - PBKDF2 (
pbkdf2-sha1,pbkdf2-sha256,pbkdf2-sha512)
- Argon2 (
- Tested using various vectors generated from other implementations
- Bun.password.hash
- Zig std.crypto.pwhash
@phc/*packages (implemented by simonepri)
- Uses PHC string format
- (planned) CLI to generate hash
- (planned) CLI to measure hashing performance
Usage
Installation
Hash of Lyra is available on NPM registry:
$ bun add hash-of-lyra # Bun
$ npm install hash-of-lyra # NPMAPI
hash(password, params)
Generates a hash string from the provided password using algorithm and
parameters defined in params.
import { hash } from "hash-of-lyra";
const password = "friedrice";
// All Argon2 algorithms uses the following parameters:
// - m: memory cost
// - t: time cost
// - p: degree of parallelism
// - keylen (optional): length of generated key (default = 32)
// - salt (optional): salt for the password (default = cryptographically random 32 bytes)
await hash(password, {
algorithm: "argon2id",
m: 12288,
t: 3,
p: 1,
});
await hash(password, {
algorithm: "argon2i",
m: 12288,
t: 3,
p: 1,
});
await hash(password, {
algorithm: "argon2d",
m: 12288,
t: 3,
p: 1,
});
await hash(password, {
algorithm: "argon2id",
m: 12288,
t: 3,
p: 1,
keylen: 16,
salt: Buffer.from("random"),
});
// Scrypt algorithm uses the following parameters:
// - lN: memory cost exponent (actual memory cost is 2^lN)
// - r: block size
// - p: degree of parallelism
// - keylen (optional): length of generated key (default = 32)
// - salt (optional): salt for the password (default = cryptographically random 16 bytes)
// - maxmem (optional): memory usage limit (default = 32 * 1024 * 1024)
await hash(password, { algorithm: "scrypt", lN: 15, r: 8, p: 3 });
await hash(password, {
algorithm: "scrypt",
lN: 15,
r: 8,
p: 3,
keylen: 64,
salt: Buffer.from("random"),
maxmem: 128 * 1024 * 1024,
});
// PBKDF2 algorithm uses the following parameter:
// - i: number of iterations
// - digest: HMAC digest algorithm to use (available values: 'sha256', 'sha512')
// - keylen (optional): length of generated key (default = 32 (SHA-256), 64 (SHA-512))
// - salt (optional): salt for the password (default = cryptographically random 16 bytes)
await hash(password, {
algorithm: "pbkdf2",
i: 600_000,
digest: "sha256",
});
await hash(password, {
algorithm: "pbkdf2",
i: 210_000,
digest: "sha512",
});
await hash(password, {
algorithm: "pbkdf2",
i: 210_000,
digest: "sha512",
keylen: 64,
salt: Buffer.from("random"),
});Password hashing parameters
The best practice for hashing passwords is to tune the parameters to match the system performance. Stronger parameters will make the password more difficult to crack in the event of leakage, but it will also cause performance slowdown and make the system vulnerable to DoS attacks. Ideally, the parameters should also be updated over time as the system evolves or moved into new machines/deployment platforms.
Therefore, we decided that hash() should not provide any default parameters,
except for non-performance related values such as generated key length. It is
the programmer's responsibility to provide the algorithm choice and parameters
suitable for their system.
However, we provide several recommendations that you can import from the package:
ARGON2ID_OWASP_2026_HICPUARGON2ID_OWASP_2026_HIMEMARGON2I_OWASP_2026_HICPUARGON2I_OWASP_2026_HIMEMARGON2D_OWASP_2026_HICPUARGON2D_OWASP_2026_HIMEMSCRYPT_OWASP_2026_HICPUSCRYPT_OWASP_2026_HIMEMPBKDF2_OWASP_2026_SHA256PBKDF2_OWASP_2026_SHA512
*_HICPU parameters are suitable for systems with limited memory by increasing
the CPU usage, while *_HIMEM parameters are suitable for systems with limited
CPU time by increasing the memory usage.
import {
ARGON2ID_OWASP_2026_HICPU,
ARGON2ID_OWASP_2026_HIMEM,
} from "hash-of-lyra";
await hash("password", "argon2id", ARGON2ID_OWASP_2026_HICPU);
await hash("password", "argon2id", ARGON2ID_OWASP_2026_HIMEM);References:
verify(phcString, password, opts?)
Given a password hash string in PHC format, returns true
if password matches the hash and false otherwise.
import { verify } from "hash-of-lyra";
const phcstring = "$pbkdf2-sha1$i=4096$c2FsdA$SwB5AbdlSJq+rUnZJvch0GWkKcE";
await verify(phcstring, "password"); // true
await verify(phcstring, "pasword"); // falseopts is additional options that can be passed to the underlying algorithms:
scryptMaxmem: passed tomaxmemoption for Scrypt algorithm
Errors
HashError
Thrown when the hash algorithm function call throws an error, such as OOM or invalid parameters provided.
Properties:
cause: any: underlying error
try {
// Will throw error because the memory limit is only 1 MiB
await hash(password, "scrypt", {
lN: 20,
r: 8,
p: 3,
keylen: 64,
maxmem: 1 * 1024 * 1024,
});
} catch (error) {
console.error(error);
// HashError: Hash function throws an error
console.log(error.cause);
// RangeError: Invalid scrypt params: error:030000AC:digital envelope routines::memory limit exceeded
}UnknownAlgorithmError
Thrown by verify() when the PHC string uses an unknown algorithm.
try {
// hash-of-lyra does not implement `custom` algorithm
const phcstring = "$custom$a=1,b=2,c=3$UGhhZXRob24K$Vml2aWFuIGJlc3QgZ2lybAo=";
await verify(phcstring, "password");
} catch (error) {
console.error(error);
// UnknownAlgorithmError: Unknown algorithm: custom
console.log(error.id); // custom
}Properties:
id: string: algorithm name from the PHC string
InvalidPHCStringError
Thrown by verify() when the PHC string is not valid (e.g. missing some
parameters).
try {
// Parameter m is missing
const phcstring =
"$argon2id$v=19$t=4,p=2$xHcElztEnwcblhSU29Z0+giGsdnlqA6wSNNd2CTydrU$xEW8n/nvs5cybwBUpzKG2zm4gqQufIIJyS0PS1z5AtM";
await verify(phcstring, "password");
} catch (error) {
console.error(error);
// InvalidPHCStringError: Missing parameter for argon2id: m
console.log(error.info);
// {
// id: "argon2id",
// version: 19,
// params: { t: 4, p: 2 },
// salt: <Buffer c4 77 04 97 ...>,
// hash: <Buffer c4 45 bc 9f ...>
// }
}Properties:
info: parsed information from the PHC stringinfo.id: string: algorithm name (e.g.argon2id,pbkdf2-sha256)info.version?: number: version number (e.g.16)info.params: Record<string, string | number>: algorithm parameters (e.g.{ m: 1024, t: 4, p: 2 }), can be empty objectinfo.salt?: Uint8Array: salt valueinfo.hash?: Uint8Array: hash value
AlgorithmNotAvailableError
Thrown if the environment does not support the algorithm, although
hash-of-lyra officially supports the algorithm.
try {
// crypto.argon2 is only available in Node 24.7+
await hash("password", "argon2id", { m: 12288, t: 3, p: 1 });
} catch (error) {
console.error(error);
// AlgorithmNotAvailableError: crypto.argon2 is not available
}
try {
// crypto.argon2 is only available in Node 24.7+
const phcstring =
"$argon2id$v=19$m=1024,t=4,p=2$xHcElztEnwcblhSU29Z0+giGsdnlqA6wSNNd2CTydrU$xEW8n/nvs5cybwBUpzKG2zm4gqQufIIJyS0PS1z5AtM";
await verify(phcstring, "password");
} catch (error) {
console.error(error);
// AlgorithmNotAvailableError: crypto.argon2 is not available
}CLI
hash-of-lyra generate
TBD
hash-of-lyra benchmark
TBD
Notes
PBKDF2 with SHA1 algorithm
SHA1 is a vulnerable digest algorithm and can only be used for verifying legacy password hashes. The library will not allow hashing new passwords with SHA1 digest.
const phc = await hash("password", {
algorithm: "pbkdf2",
digest: "sha1",
i: 1000,
});
// AlgorithmNotAvailableError: PBKDF2 hash with SHA1 is not allowedSupported environments
| Environment | Argon2 | Scrypt | PBKDF2 |
| -------------------------------------------- | ---------------- | ------ | ------ |
| Node.js | ❎[1] | ✅ | ✅ |
| Bun | ✅[2] | ✅ | ✅ |
| Cloudflare Workers (with nodejs_compat) | TBD | TBD | TBD |
| Cloudflare Workers (without nodejs_compat) | TBD | TBD | TBD |
[1]
crypto.argon2is available on Node.js 24.7.0+.
[2]
Bun.password.hashArgon2 implementation does not support passingp,salt, andkeylen, so hashes generated in Bun will always havep = 1, randomly generated salt, and fixed hash length (32 bytes).
License
MIT License
