Password hashing with Bun.password, no bcrypt dependency
Published November 13, 2026
export const hashPassword = (password: string) =>
Bun.password.hash(password, { algorithm: "argon2id", memoryCost: 19456, timeCost: 2 });
export const verifyPassword = (password: string, hash: string) => Bun.password.verify(password, hash);
const hash = await hashPassword("correct horse battery staple");
console.log(await verifyPassword("correct horse battery staple", hash)); // true
console.log(await verifyPassword("wrong guess", hash)); // falseWhat
Bun.password.hash(password, options) hashes a password with argon2 (default) or bcrypt and
returns a self-describing string with the algorithm, parameters, and salt baked in.
Bun.password.verify(password, hash) reads that string back and checks a candidate password
against it.
Why it matters
Hashing passwords in Node almost always means installing bcrypt (native bindings, a
postinstall build step) or bcryptjs (pure JS, slower). Bun implements both argon2 and bcrypt
natively in the runtime, so Bun.password needs no dependency and no native compile step.
Argon2 is also the more modern default — it won the Password Hashing Competition and is
generally recommended over bcrypt for new code — and Bun defaults to it while still supporting
bcrypt for compatibility with existing hashes from another system.
How it works
hashPasswordsetsalgorithm: "argon2id"explicitly (argon2id is also the library default) along withmemoryCostandtimeCost, the two knobs that trade hashing time for resistance to GPU-accelerated cracking attempts.- The salt is generated automatically and stored inside the returned hash string — there's no separate salt to manage or persist yourself.
verifyPassworddoesn't need to know which algorithm or parameters produced the hash:verifyparses that out of the hash string itself, so changinghashPassword's settings later doesn't invalidate the ability to verify old hashes.- Both functions are async by default because hashing is deliberately slow (that's the whole
point);
Bun.password.hashSync/verifySyncexist too, but block the event loop for the duration of the hash.
Gotchas
- Never compare passwords or hashes with
===— always go throughBun.password.verify, which is written to run in constant time regardless of where the strings first differ. - Higher
memoryCost/timeCost(argon2) orcost(bcrypt) means slower login for legitimate users too — pick values based on your server's actual hardware, not just security tables written for a specific reference machine. - bcrypt silently truncates passwords over 72 bytes in most implementations; Bun's bcrypt mode avoids that specific footgun by hashing longer inputs with SHA-512 first, but it's still worth knowing bcrypt has that limit if you compare hashes across systems.
Related
Bun.CryptoHasher— general-purpose cryptographic hashing (SHA-256, etc.) for data integrity, not passwords; it has no built-in salting or slow-hashing and shouldn't be used for credentials.- Session tokens after login are a separate concern from password hashing —
Bun.passwordonly covers verifying the credential itself.