PBKDF2-SHA password hashing, zero dependencies, pure JavaScript — with a bcrypt-compatible API.
beecrypt is a modern password hashing library that uses PBKDF2 with the SHA-2 family (SHA-256, SHA-384, SHA-512). It works everywhere — Bun, Node, Deno, and the browser — with no native bindings, no dependencies, and no build step.
The API mirrors the popular bcrypt npm package, making beecrypt a drop-in replacement for projects that want to move away from the Blowfish cipher without rewriting authentication code.
| bcrypt | beecrypt | |
|---|---|---|
| Algorithm | Blowfish cipher (obsolete) | PBKDF2 + SHA-2 (NIST standard) |
| Hash format | $2b$... |
$beecrypt$SHA-512$k=64&i=210000$... |
| Dependencies | node-gyp, native C++ build |
Zero — pure JS, no native code |
| Platforms | Node only (needs node-gyp) |
Bun, Node, Deno, browser |
| Sync support | Blocking C++ call | Non-blocking async + true sync fallback |
| Flexibility | Fixed 184-bit output, cost only | Configurable digest, key length, salt length, iterations |
| Auditability | Complex Blowfish key schedule | Standard PBKDF2 — well-studied, FIPS-approved |
- bcrypt-compatible API —
hash,compare,hashSync,compareSync,genSalt,genSaltSync,getRounds,getSalt— all with the same signatures. - Pure JavaScript — zero dependencies, no
node-gyp, no native compilation. - Universal — runs in Bun, Node.js, Deno, and modern browsers.
- Auto-backend — uses Web Crypto
SubtleCrypto.deriveBits()when available, falls back to a hand-optimized pure-JS PBKDF2 implementation. - Configurable — choose digest (SHA-256/384/512), key length, salt length, and iteration count.
- Constant-time verification — timing-safe comparison prevents side-channel attacks.
- Self-contained hash format — every hash encodes its own parameters (digest, key length, iterations, salt), so upgrading settings doesn't break existing hashes.
needsRehash()— detect when a hash uses weaker parameters than your current policy.- Synchronous API —
hashSync/compareSyncuse the pure-JS PBKDF2 backend with no event-loop blocking from native code.
bun add beecrypt
npm install beecrypt
pnpm add beecrypt
yarn add beecrypt
import { hash, compare, genSalt } from "beecrypt";
// Hash a password (bcrypt-compatible style)
const salt = await genSalt(12);
const hashed = await hash("hunter2", salt);
console.log(hashed);
// → $beecrypt$SHA-512$k=64&i=12$pX6c9dQ...$8f3a...
// Verify
const match = await compare("hunter2", hashed); // true
const wrong = await compare("wrong", hashed); // falseimport { hash, compare } from "beecrypt";
// Uses SHA-512, 210000 iterations, 64-byte key, 32-byte salt
const hashed = await hash("mypassword");
const ok = await compare("mypassword", hashed); // true- password
string— the password to hash. - saltOrRounds
string | number | undefined— either a salt string fromgenSalt, a round count (passed togenSaltinternally), orundefinedto use default parameters. - returns
Promise<string>— the encoded hash.
await hash("pw"); // default params
await hash("pw", 12); // 12 rounds → genSalt(12) → hash with that salt
await hash("pw", saltString); // use a specific salt- password
string - encrypted
string— a hash previously produced byhash. - returns
Promise<boolean>
await compare("mypassword", hashed); // true or falseSynchronous version of hash. Uses the pure-JS PBKDF2 backend; does not block on native code.
const hashed = hashSync("pw"); // default params
const hashed = hashSync("pw", 12); // 12 rounds
const hashed = hashSync("pw", salt); // explicit saltSynchronous version of compare.
const ok = compareSync("pw", hashed);- rounds
number(default10) — iteration count stored in the salt string. - returns
Promise<string>— a salt string that can be passed tohash.
const salt = await genSalt(12);
// → $beecrypt$SHA-512$k=64&i=12$base64salt$Synchronous version of genSalt.
const salt = genSaltSync(12);Returns the iteration count stored in a hash.
getRounds(hashed); // → 210000Returns the salt prefix of a hash (usable with hash).
const salt = getSalt(hashed);
const rehashed = await hash("pw", salt);Alias for compare. Exists for backward compatibility with the original beecrypt API.
await verify("pw", hashed);Returns true if the hash uses weaker parameters than the current default settings.
needsRehash(hashed);For multiple configurations or instance-specific control.
import { Beecrypt } from "beecrypt";
const bc = new Beecrypt({
digest: "SHA-256",
iterations: 100_000,
keyBytes: 32,
saltBytes: 16,
});
const hashed = await bc.hash("pw");
const ok = await bc.verify("pw", hashed);
bc.needsRehash(hashed); // false (matches instance config)
bc.configure({ iterations: 200_000 });
bc.needsRehash(hashed); // true (iterations increased)$beecrypt$<digest>$k=<keyBytes>&i=<iterations>$<salt>$<hash>
Example:
$beecrypt$SHA-512$k=64&i=210000$pX6c9dQ3...$8f3aBc1...
| Part | Description |
|---|---|
beecrypt |
Format identifier |
SHA-512 |
Digest algorithm (SHA-256, SHA-384, or SHA-512) |
k=64 |
Derived key length in bytes |
i=210000 |
PBKDF2 iteration count |
salt |
Base64url-encoded random salt |
hash |
Base64url-encoded derived key |
All parameters are embedded in the hash string, so changing settings never breaks verification of existing hashes.
| Option | Default | Description |
|---|---|---|
iterations |
210_000 |
PBKDF2 iteration count |
saltBytes |
32 |
Random salt length (bytes) |
keyBytes |
64 |
Derived key length (bytes) |
digest |
"SHA-512" |
Hash function |
For PBKDF2-SHA-512, a good starting point in 2026 is 200,000–300,000 iterations (the default of 210,000 targets ~1 second hash time on modern hardware). Adjust based on your performance requirements:
// Faster hashing, less security margin
const bc = new Beecrypt({ iterations: 100_000 });
// Slower hashing, more security margin
const bc = new Beecrypt({ iterations: 600_000 });Use needsRehash() to enforce a minimum iteration count as hardware improves over time.
- SHA-512 (default) — best security margin. 64-byte internal state, 64-byte output.
- SHA-384 — reduced output (48 bytes), useful when truncating to fit legacy fields.
- SHA-256 — 32-byte output, matching e.g. HMAC-SHA-256 expectations.
SHA-512 is recommended for all new applications. It has a larger internal state than SHA-256, making it more resistant to length-extension and multi-target attacks.
const bc = new Beecrypt({
digest: "SHA-512",
iterations: 210_000,
keyBytes: 64,
saltBytes: 32,
});
await bc.hash("password");
await bc.verify("password", hash);
bc.needsRehash(hash);
bc.configure({ iterations: 300_000 });Step 1: replace imports.
-import { hash, compare } from "bcrypt";
+import { hash, compare } from "beecrypt";Step 2: existing bcrypt hashes ($2b$...) can be stored alongside beecrypt hashes. beecrypt does not verify bcrypt hashes — you'll need to rehash users on next login:
import { compare } from "bcrypt";
import { hash, compare as beecryptCompare } from "beecrypt";
async function login(password, storedHash) {
if (storedHash.startsWith("$2b$")) {
const ok = await compare(password, storedHash);
if (ok) {
const newHash = await hash(password);
// save newHash to DB
}
return ok;
}
return beecryptCompare(password, storedHash);
}| Runtime | Web Crypto | Pure JS fallback |
|---|---|---|
| Bun | Yes | Yes |
| Node.js 18+ | Yes | Yes |
| Deno | Yes | Yes |
| Browser | Yes | Yes |
The library auto-detects the best available backend. Web Crypto provides native PBKDF2 (~10–50x faster). The pure-JS fallback is a fully self-contained PBKDF2-HMAC-SHA implementation.
bun run bnch
Runs hash/verify benchmarks across multiple configurations, including direct PBKDF2 derivation with both the Web Crypto and pure-JS backends.
bun test # run tests
bun run bnch # run benchmarks
AGPL-3.0