argon2 vs bcrypt vs pbkdf2
Secure Password Hashing in Node.js Applications
argon2bcryptpbkdf2Similar 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
argon202,1511.03 MB510 months agoMIT
bcrypt07,8011.11 MB34a year agoMIT
pbkdf20202-311 days 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: argon2 vs bcrypt vs pbkdf2

  • 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.

  • 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.

  • 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.

README for argon2

node-argon2

Financial contributors on Open Collective Build status NPM package

Bindings to the reference Argon2 implementation.

Usage

It's possible to hash using either Argon2i, Argon2d or Argon2id (default), and verify if a password matches a hash.

To hash a password:

const argon2 = require('argon2');

try {
  const hash = await argon2.hash("password");
} catch (err) {
  //...
}

To see how you can modify the output (hash length, encoding) and parameters (time cost, memory cost and parallelism), read the wiki

To verify a password:

try {
  if (await argon2.verify("<big long hash>", "password")) {
    // password match
  } else {
    // password did not match
  }
} catch (err) {
  // internal failure
}

Migrating from another hash function

See this article on the wiki for steps on how to migrate your existing code to Argon2. It's easy!

TypeScript usage

A TypeScript type declaration file is published with this module. If you are using TypeScript 2.0.0 or later, that means you do not need to install any additional typings in order to get access to the strongly typed interface. Simply use the library as mentioned above.

import * as argon2 from "argon2";

const hash = await argon2.hash(..);

Prebuilt binaries

node-argon2 provides prebuilt binaries from v0.26.0 onwards. They are built every release using GitHub Actions.

The current prebuilt binaries are built and tested with the following systems:

  • Ubuntu 22.04 (x86-64; ARM64 from v0.28.2; ARMv7 from v0.43.0)
  • MacOS 13 (x86-64)
  • MacOS 14 (ARM64 from v0.29.0)
  • Windows Server 2022 (x86-64)
  • Alpine Linux 3.18 (x86-64 from v0.28.1; ARM64 from v0.28.2; ARMv7 from v0.43.0)
  • FreeBSD 14 (x86-64 from v0.29.1; ARM64 from v0.44.0)

Binaries should also work for any version more recent than the ones listed above. For example, the binary for Ubuntu 20.04 also works on Ubuntu 22.04, or any other Linux system that ships a newer version of glibc; the binary for MacOS 11 also works on MacOS 12. If your platform is below the above requirements, you can follow the Before installing section below to manually compile from source. It is also always recommended to build from source to ensure consistency of the compiled module.

Before installing

You can skip this section if the prebuilt binaries work for you.

You MUST have a node-gyp global install before proceeding with the install, along with GCC >= 5 / Clang >= 3.3. On Windows, you must compile under Visual Studio 2015 or newer.

node-argon2 works only and is tested against Node >=18.0.0.

OSX

To install GCC >= 5 on OSX, use homebrew:

$ brew install gcc

Once you've got GCC installed and ready to run, you then need to install node-gyp, you must do this globally:

$ npm install -g node-gyp

Finally, once node-gyp is installed and ready to go, you can install this library, specifying the GCC or Clang binary to use:

$ CXX=g++-12 npm install argon2

NOTE: If your GCC or Clang binary is named something different than g++-12, you'll need to specify that in the command.

FAQ

How do I manually rebuild the binaries?
$ npx @mapbox/node-pre-gyp rebuild -C ./node_modules/argon2

Run @mapbox/node-pre-gyp instead of node-gyp because node-argon2's binding.gyp file relies on variables from @mapbox/node-pre-gyp.

You can omit npx @mapbox and use just node-pre-gyp if you have a global installation of @mapbox/node-pre-gyp, otherwise prefixing npx will use the local one in ./node_modules/.bin

How do I skip installing prebuilt binaries and manually compile from source?

You can do either of the two methods below:

  1. Force build from source on install.
$ npm install argon2 --build-from-source
  1. Ignore node-argon2 install script and build manually.
$ npm install argon2 --ignore-scripts
$ npx node-gyp rebuild -C ./node_modules/argon2
I installed Node as a snap, and I can't install node-argon2.

This seems to be an issue related to snap (see #345 (comment)). Installing Node with another package manager, such as asdf or nvm, is a possible workaround.

Contributors

Code contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

Work licensed under the MIT License. Please check P-H-C/phc-winner-argon2 for license over Argon2 and the reference implementation.