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 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.
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 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.
Bindings to the reference Argon2 implementation.
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
}
See this article on the wiki for steps on how to migrate your existing code to Argon2. It's easy!
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(..);
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:
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.
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.
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.
$ 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
You can do either of the two methods below:
$ npm install argon2 --build-from-source
node-argon2 install script and build manually.$ npm install argon2 --ignore-scripts
$ npx node-gyp rebuild -C ./node_modules/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.
This project exists thanks to all the people who contribute. [Contribute].
Become a financial contributor and help us sustain our community. [Contribute]
Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]
Work licensed under the MIT License. Please check P-H-C/phc-winner-argon2 for license over Argon2 and the reference implementation.