argon2, bcrypt, and pbkdf2 are cryptographic libraries used for hashing passwords and sensitive data in Node.js environments. argon2 is the winner of the Password Hashing Competition and offers resistance against GPU cracking attacks. bcrypt is a long-standing standard based on the Blowfish cipher, widely supported across languages. pbkdf2 implements the Password-Based Key Derivation Function 2, often used for compliance with older standards like NIST. While typically backend-focused, frontend architects designing Serverless functions or Backend-for-Frontend layers must select the right tool for securing user credentials at rest.
When building authentication systems in Node.js β whether in a traditional server, a Next.js API route, or a serverless function β choosing the right password hashing algorithm is critical. argon2, bcrypt, and pbkdf2 are the three most common options available as npm packages. They all aim to protect user credentials, but they differ significantly in security model, performance, and ease of use.
argon2 is the winner of the Password Hashing Competition (2015).
// argon2: Hashing with Argon2id
const argon2 = require('argon2');
async function hashPassword(password) {
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 2 ** 16,
timeCost: 3
});
return hash;
}
bcrypt relies on the Blowfish cipher and CPU cost.
// bcrypt: Hashing with cost factor
const bcrypt = require('bcrypt');
async function hashPassword(password) {
const saltRounds = 12;
const hash = await bcrypt.hash(password, saltRounds);
return hash;
}
pbkdf2 uses HMAC with a pseudorandom function (usually SHA-256).
// pbkdf2: Hashing with iterations
const pbkdf2 = require('pbkdf2');
function hashPassword(password, callback) {
const salt = crypto.randomBytes(16);
pbkdf2.pbkdf2(password, salt, 100000, 64, 'sha512', callback);
}
// Or using Promises
const { pbkdf2Async } = require('pbkdf2/promise');
const hash = await pbkdf2Async(password, salt, 100000, 64, 'sha512');
Tuning these libraries correctly is vital. Default settings may become insecure as hardware improves.
argon2 exposes memory, time, and parallelism parameters.
argon2id variant is the default recommendation for most web apps.// argon2: Tuning parameters
const options = {
memoryCost: 65536, // 64 MB
timeCost: 3, // 3 iterations
parallelism: 4 // 4 threads
};
const hash = await argon2.hash(password, options);
bcrypt uses a single cost parameter (log2 of iterations).
// bcrypt: Tuning cost
const saltRounds = 12; // Takes ~200ms on modern CPU
const hash = await bcrypt.hash(password, saltRounds);
pbkdf2 requires manual management of salt, iterations, and key length.
// pbkdf2: Manual salt and iteration management
const salt = crypto.randomBytes(16).toString('hex');
const iterations = 100000;
const keylen = 64;
const digest = 'sha512';
const hash = await pbkdf2Async(password, salt, iterations, keylen, digest);
// Store salt + hash together for verification later
Verifying a password against a stored hash is a common operation during login.
argon2 stores parameters inside the hash string.
// argon2: Verification
const isValid = await argon2.verify(storedHash, candidatePassword);
if (isValid) {
// Login successful
}
bcrypt also embeds the salt and cost in the hash string.
// bcrypt: Verification
const isValid = await bcrypt.compare(candidatePassword, storedHash);
if (isValid) {
// Login successful
}
pbkdf2 does not embed parameters in a standard string format.
// pbkdf2: Verification
// You must retrieve salt, iterations, etc. from your DB
const computedHash = await pbkdf2Async(password, storedSalt, iterations, keylen, digest);
const isValid = crypto.timingSafeEqual(Buffer.from(storedHash), Buffer.from(computedHash));
Deployment environment constraints often dictate which package you can use.
argon2 requires native compilation.
npm install.# argon2: Installation may require build tools
npm install argon2
# Error if python/make/g++ are missing
bcrypt has native bindings but offers a pure JS fallback.
bcryptjs variant is pure JS but slower.bcrypt package tries to compile native code for speed.argon2 in many cases.# bcrypt: Installation
npm install bcrypt
# Falls back to JS if native compilation fails
pbkdf2 is pure JavaScript.
crypto.pbkdf2 which is faster and preferred over this npm package.# pbkdf2: Installation
npm install pbkdf2
# Or use built-in: require('crypto').pbkdf2
You are building a new user platform from scratch.
argon2// Use argon2id for best balance
const hash = await argon2.hash(password, { type: argon2.argon2id });
You are maintaining an older system with existing bcrypt hashes.
bcrypt// Check existing bcrypt hash
const isValid = await bcrypt.compare(password, user.legacyHash);
Your organization mandates FIPS 140-2 compliance.
pbkdf2 (or built-in crypto)// Use built-in crypto for FIPS
const hash = crypto.pbkdf2Sync(password, salt, iterations, keylen, 'sha256');
These libraries are for password hashing, not general encryption.
crypto or libsodium).pbkdf2 npm package if you are on modern Node.js β use the built-in crypto module instead for better performance.bcrypt with a cost factor below 10 β it is too fast and insecure.| Feature | argon2 | bcrypt | pbkdf2 |
|---|---|---|---|
| Security | π High (Memory-hard) | π‘οΈ Medium (CPU-bound) | π‘οΈ Medium (CPU-bound) |
| Speed | π’ Configurable (Slow) | π’ Configurable (Slow) | π Fast (Needs high iterations) |
| Setup | π§ Native Build Required | π§ Native + JS Fallback | β Pure JS (or Built-in) |
| Verification | β Embedded Params | β Embedded Params | β Manual Param Mgmt |
| Compliance | β Not FIPS | β Not FIPS | β FIPS Approved |
Think in terms of security posture and deployment constraints:
argon2 with the argon2id variant. It is the modern standard.bcrypt. It is everywhere and well understood.pbkdf2 (preferably via Node.js crypto module).For most new Node.js projects in 2024, argon2 is the clear winner if your environment supports it. It offers better protection against hardware-accelerated attacks without sacrificing developer experience. However, bcrypt remains a safe and valid choice for many applications, especially where simplicity and compatibility are key.
Choose pbkdf2 primarily for regulatory compliance where specific algorithms are mandated, or when working in environments that cannot install native modules easily. For general Node.js projects, prefer the built-in crypto module over this npm package unless you specifically need a pure JS implementation for browser parity.
Choose bcrypt if you need maximum compatibility with legacy systems or existing databases. It is pure JavaScript compatible (with a native fallback) and works well in environments where installing build tools for argon2 is difficult. It remains secure if configured with sufficient cost factors.
Choose argon2 for new projects requiring top-tier security against modern hardware attacks. It is the current industry standard for password hashing and offers configurable memory hardness. Use this when you control the deployment environment and can install native dependencies.
This library provides the functionality of PBKDF2 with the ability to use any supported hashing algorithm returned from crypto.getHashes()
var pbkdf2 = require('pbkdf2')
var derivedKey = pbkdf2.pbkdf2Sync('password', 'salt', 1, 32, 'sha512')
...
For more information on the API, please see the relevant Node documentation.
For high performance, use the async variant (pbkdf2.pbkdf2), not pbkdf2.pbkdf2Sync; this variant has the oppurtunity to use window.crypto.subtle when browserified.
This module is a derivative of cryptocoinjs/pbkdf2-sha256, so thanks to JP Richardson for laying the ground work.
Thank you to FangDun Cai for donating the package name on npm, if you're looking for his previous module it is located at fundon/pbkdf2.