@portable/password
Functions
Password.hash
Hashes a password with PBKDF2-HMAC-SHA256 and a random 16-byte salt, and returns a 32-byte hash in PHC string format. A string password is normalized to NFC and encoded as UTF-8, and bytes are hashed as they are.
async function hash(
password: string | Uint8Array,
options: hash.Options = {},
): Promise<string>;| Option | Type | Default | Description |
|---|---|---|---|
iterations | number | 600000 | PBKDF2 iterations. Cloudflare Workers rejects more than 100,000. |
maxBytes | number | 4096 | Longest password accepted, in UTF-8 bytes after NFC normalization. |
signal | AbortSignal | Rejects the returned promise with signal.reason when aborted. WebCrypto can't stop a derivation that has started, so the derivation finishes in the background and its result is dropped. |
Throws
MaxBytesErrorWhen the password is longer thanmaxBytes.
import * as Password from '@portable/password';
await Password.hash('correct horse battery staple');
// '$pbkdf2-sha256$i=600000$ZmFrZXNhbHRmYWtlc2FsdA$...'
await Password.hash(password, { iterations: 100_000 }); // the most Cloudflare Workers acceptsPassword.needsRehash
Returns true when a stored hash is weaker than what hash writes with the same iterations:
fewer iterations, a digest other than SHA-256, a salt shorter than 16 bytes, or a hash shorter
than 32 bytes. Call it after a successful verify and store a new hash of the password when
it returns true.
function needsRehash(
stored: string,
options: needsRehash.Options = {},
): boolean;| Option | Type | Default | Description |
|---|---|---|---|
iterations | number | 600000 | Iterations a current hash has at least. |
Throws
InvalidInputErrorWhenstoredis not a well-formed PBKDF2 hash.UnsupportedErrorWhenstoredis another algorithm's hash.
import * as Password from '@portable/password';
if (await Password.verify(password, user.hash)) {
if (Password.needsRehash(user.hash)) {
await saveHash(user, await Password.hash(password));
}
}Password.verify
Checks a password against a stored PBKDF2 hash and compares the result in constant time.
Reads the PHC format that hash writes ($pbkdf2-sha256$i=600000$...) and passlib's
pbkdf2_sha1, pbkdf2_sha256 and pbkdf2_sha512 format ($pbkdf2-sha256$29000$...).
async function verify(
password: string | Uint8Array,
stored: string,
options: verify.Options = {},
): Promise<boolean>;| Option | Type | Default | Description |
|---|---|---|---|
maxBytes | number | 4096 | Longest password accepted, in UTF-8 bytes after NFC normalization. |
signal | AbortSignal | Rejects the returned promise with signal.reason when aborted. WebCrypto can't stop a derivation that has started, so the derivation finishes in the background and its result is dropped. |
Throws
InvalidInputErrorWhenstoredis not a well-formed PBKDF2 hash.UnsupportedErrorWhenstoredis another algorithm's hash, such as$argon2id$....MaxBytesErrorWhen the password is longer thanmaxBytes.
import * as Password from '@portable/password';
const stored = await Password.hash('hunter2');
await Password.verify('hunter2', stored); // true
await Password.verify('hunter3', stored); // falseErrors
Password.InvalidInputError
Thrown by verify and needsRehash for a stored hash that is malformed: a PBKDF2 hash in PHC
or passlib format with a missing field or a bad value, or a string without a $<algorithm>$
prefix.
class InvalidInputError extends Errors.InvalidInputError {
readonly kind: string;
}import * as Password from '@portable/password';
try {
await Password.verify(password, '$pbkdf2-sha256$i=0$...');
} catch (error) {
if (error instanceof Password.InvalidInputError) {
console.log('malformed hash');
}
}Password.MaxBytesError
Thrown by hash and verify when the password is longer than maxBytes in UTF-8.
class MaxBytesError extends Errors.MaxBytesError {
constructor(options: Errors.MaxBytesError.Options);
readonly kind: string;
}import * as Password from '@portable/password';
try {
await Password.hash('x'.repeat(5000));
} catch (error) {
if (error instanceof Password.MaxBytesError) {
console.log(`limit is ${error.maxBytes} bytes`);
}
}Password.UnsupportedError
Thrown by verify and needsRehash for a hash from another algorithm, such as bcrypt
($2b$...), scrypt or Argon2 ($argon2id$...). WebCrypto can't compute those, so verify them
with another library while you migrate.
class UnsupportedError extends Errors.UnsupportedError {
readonly kind: string;
}import * as Password from '@portable/password';
try {
return await Password.verify(password, stored);
} catch (error) {
if (error instanceof Password.UnsupportedError) {
return verifyLegacy(password, stored);
}
throw error;
}