pbkdf2 vs bcrypt vs argon2
Secure Password Hashing in Node.js Applications
pbkdf2bcryptargon2Similar Packages:

Secure Password Hashing in Node.js Applications

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
pbkdf27,487,01620347.9 kB3a month agoMIT
bcrypt3,817,3287,8051.11 MB38a year agoMIT
argon21,667,9012,194959 kB43 months agoMIT

Secure Password Hashing: Argon2 vs Bcrypt vs PBKDF2

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.

πŸ›‘οΈ Algorithm Security: Memory Hardness vs CPU Cost

argon2 is the winner of the Password Hashing Competition (2015).

  • It is memory-hard, meaning it requires significant RAM to compute.
  • This makes it resistant to GPU and ASIC cracking attacks.
  • It supports three variants: Argon2d, Argon2i, and Argon2id (recommended).
// 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.

  • It is not memory-hard, making it more vulnerable to GPU attacks than Argon2.
  • It is battle-tested and widely adopted since 1999.
  • Security relies on increasing the "cost factor" (work factor).
// 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).

  • It is CPU-bound and not memory-hard.
  • It is NIST approved and often required for compliance (e.g., FIPS).
  • Vulnerable to parallel hardware attacks if iteration count is too low.
// 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');

βš™οΈ Configuration and Tuning

Tuning these libraries correctly is vital. Default settings may become insecure as hardware improves.

argon2 exposes memory, time, and parallelism parameters.

  • You must balance memory usage to avoid DoS attacks on your server.
  • The 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).

  • A cost of 10 is often too low for modern hardware.
  • Aim for a cost that takes ~200ms to hash on your production hardware.
// 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.

  • You must generate and store the salt separately (or alongside the hash).
  • Iteration counts should be at least 100,000 for SHA-256.
// 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

πŸ”„ Verification Process

Verifying a password against a stored hash is a common operation during login.

argon2 stores parameters inside the hash string.

  • You do not need to pass options during verification.
  • The library parses the hash to retrieve salt and settings.
// argon2: Verification
const isValid = await argon2.verify(storedHash, candidatePassword);
if (isValid) {
  // Login successful
}

bcrypt also embeds the salt and cost in the hash string.

  • Verification is simple and stateless.
  • It automatically extracts the salt from the stored hash.
// bcrypt: Verification
const isValid = await bcrypt.compare(candidatePassword, storedHash);
if (isValid) {
  // Login successful
}

pbkdf2 does not embed parameters in a standard string format.

  • You must store the salt, iterations, and hash separately.
  • Verification requires reconstructing the hash with stored parameters.
// 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));

πŸ“¦ Installation and Dependencies

Deployment environment constraints often dictate which package you can use.

argon2 requires native compilation.

  • It needs Python and a C++ compiler installed during npm install.
  • This can be tricky in some serverless or restricted Docker environments.
  • Pre-built binaries are available for common platforms but not all.
# 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.

  • The bcryptjs variant is pure JS but slower.
  • The main bcrypt package tries to compile native code for speed.
  • Easier to deploy than argon2 in many cases.
# bcrypt: Installation
npm install bcrypt
# Falls back to JS if native compilation fails

pbkdf2 is pure JavaScript.

  • No compilation required.
  • Works in any Node.js environment without build tools.
  • Note: Node.js has a built-in crypto.pbkdf2 which is faster and preferred over this npm package.
# pbkdf2: Installation
npm install pbkdf2
# Or use built-in: require('crypto').pbkdf2

🌐 Real-World Scenarios

Scenario 1: New SaaS Application

You are building a new user platform from scratch.

  • βœ… Best choice: argon2
  • Why? It provides the highest security margin against cracking. You control the infrastructure and can install native dependencies.
// Use argon2id for best balance
const hash = await argon2.hash(password, { type: argon2.argon2id });

Scenario 2: Legacy System Migration

You are maintaining an older system with existing bcrypt hashes.

  • βœ… Best choice: bcrypt
  • Why? You cannot re-hash all existing passwords immediately. You must support the old format while migrating users gradually.
// Check existing bcrypt hash
const isValid = await bcrypt.compare(password, user.legacyHash);

Scenario 3: Compliance Restricted Environment

Your organization mandates FIPS 140-2 compliance.

  • βœ… Best choice: pbkdf2 (or built-in crypto)
  • Why? Argon2 and Bcrypt are not FIPS approved. PBKDF2 with HMAC-SHA256 is compliant.
// Use built-in crypto for FIPS
const hash = crypto.pbkdf2Sync(password, salt, iterations, keylen, 'sha256');

⚠️ When Not to Use These

These libraries are for password hashing, not general encryption.

  • Do not use for encrypting data you need to decrypt later (use crypto or libsodium).
  • Do not use pbkdf2 npm package if you are on modern Node.js β€” use the built-in crypto module instead for better performance.
  • Do not use bcrypt with a cost factor below 10 β€” it is too fast and insecure.

πŸ“Œ Summary Table

Featureargon2bcryptpbkdf2
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

πŸ’‘ Final Recommendation

Think in terms of security posture and deployment constraints:

  • Maximum Security? β†’ Use argon2 with the argon2id variant. It is the modern standard.
  • Maximum Compatibility? β†’ Use bcrypt. It is everywhere and well understood.
  • Compliance Required? β†’ Use 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.

How to Choose: pbkdf2 vs bcrypt vs argon2

  • pbkdf2:

    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.

  • bcrypt:

    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.

  • argon2:

    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.

README for pbkdf2

pbkdf2 Version Badge

This library provides the functionality of PBKDF2 with the ability to use any supported hashing algorithm returned from crypto.getHashes()

github actions coverage dependency status dev dependency status License Downloads

npm badge

Usage

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.

Credits

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.