FinepherFinepher
AR
Cybersecurity 7 min read

Still Using Bcrypt in 2026? Here's Why (and When) to Switch to Argon2

Asha Vardhan

Asha Vardhan

August 21, 2026

If your app stores passwords, this decision matters more than almost anything else in your codebase. Get it right, and a database breach is an inconvenience. Get it wrong, and it's a front-page problem.

Let's break down the two most popular password hashing algorithms - bcrypt and Argon2 - in plain language, then walk through exactly how to use each one in Node.js.

First, a quick refresher: what is password hashing?

You never want to store a user's actual password in your database. Instead, you run it through a hashing function - a one-way scramble that turns "MyPassword123" into something like $2b$12$KIXQ.... There's no "unscramble" button. When someone logs in, you hash what they typed and check if it matches the stored hash.

This is different from encryption, which can be reversed with a key. Hashing is meant to be a dead end - even for you, the developer.

But here's the catch: a fast hash is a bad password hash. If an attacker steals your database, a fast algorithm lets them try billions of guesses per second. That's why bcrypt and Argon2 exist - they're deliberately, tunably slow.

Bcrypt: the veteran

Bcrypt has been around since 1999, built on the Blowfish cipher by Niels Provos and David Mazières. It's the algorithm most developers reach for by default, and for good reason - it's had over two decades of scrutiny with no practical breaks found.

How it works: Bcrypt uses a "cost factor" (also called salt rounds) - a number like 10 or 12 that determines how many times the algorithm iterates internally. Each increment doubles the work, so cost 12 takes roughly twice as long as cost 11.

Where it falls short:

  • It caps passwords at 72 bytes - anything longer gets silently truncated, which can be a subtle bug if you don't handle it.

  • It's "CPU-hard" but not "memory-hard." It only needs a small, fixed amount of memory no matter how high you set the cost factor. That means attackers with modern GPU or ASIC hardware can run many parallel guesses efficiently - something that wasn't a real threat in 1999 but is very real today.

Is it still safe? Yes, mostly. At a cost factor of 12 or higher, bcrypt is still considered acceptable for production use in 2026. It's just no longer the strongest option available.

Argon2: the modern standard

Argon2 won the Password Hashing Competition in 2015 - an open competition specifically designed to find bcrypt's successor - and was formally standardized as RFC 9106 in 2021. It's now the top recommendation in OWASP's Password Storage Cheat Sheet.

Argon2 comes in three flavors:

  • Argon2d - fastest, but more exposed to side-channel timing attacks.

  • Argon2i - designed to resist side-channel attacks, slightly slower.

  • Argon2id - a hybrid of both, and the one you should almost always use.

What makes it different: Argon2 is memory-hard. Instead of just tuning "how many rounds," you tune three separate dials:

  • Memory cost - how much RAM each hash requires (e.g., 19–64 MiB)

  • Time cost - how many iterations to run

  • Parallelism - how many threads run in parallel

Forcing an attacker to allocate real memory for every guess is what makes Argon2 so much harder to brute-force at scale than bcrypt - GPUs and ASICs are built to be fast and cheap on memory-light workloads, not memory-heavy ones.

Argon2 vs bcrypt: head to head

Bcrypt

Argon2id

Released

1999

2015 (RFC 9106 in 2021)

Resistance to GPU/ASIC attacks

Weaker - low, fixed memory use

Strong - memory-hard by design

Tunable parameters

1 (cost factor)

3 (memory, time, parallelism)

Password length limit

72 bytes (silent truncation)

No practical limit

OWASP recommendation

Acceptable for existing systems

Preferred for new systems

Ecosystem maturity

Extremely wide - every language, every stack

Strong and growing, slightly newer bindings

Best for

Legacy systems, environments needing broad compatibility

New projects, anything security-sensitive

So which one should you pick?

Use bcrypt if:

  • You already have a bcrypt-hashed user base and your cost factor is 12 or higher - there's no urgency to migrate.

  • You're working in an environment with limited or inconsistent Argon2 library support.

  • You're not a high-value target (e.g., not a financial platform or password manager).

Use Argon2id if:

  • You're building something new. This is the easy default answer in 2026.

  • You're in a high-value or high-risk category - fintech, healthcare, anything holding sensitive data.

  • You want a future-proof setup you can simply "turn up" (more memory, more time) as hardware improves.

One important caveat if you're deploying to serverless environments (AWS Lambda, Cloudflare Workers, etc.): Argon2's memory requirement is real memory your function has to reserve. On a default 128 MB function, OWASP's standard 64 MiB profile eats half your available memory. In that case, scale the memory cost down (or bump your function's memory allocation) rather than abandoning Argon2 altogether.

Migrating from bcrypt to Argon2 later? You don't need to force a password reset. The standard pattern is rehash on login: verify the password with bcrypt as usual, and if it succeeds, immediately re-hash the plaintext password with Argon2id and save the new hash. Over a few months, your active users migrate themselves.


How to use both in Node.js

Option 1: Bcrypt

Install the native package:

npm install bcrypt

Hashing a password (on signup):

const bcrypt = require('bcrypt');

const SALT_ROUNDS = 12; // 12 is a solid default for 2026

async function hashPassword(plainPassword) {
  return await bcrypt.hash(plainPassword, SALT_ROUNDS);
}

Verifying a password (on login):

async function verifyPassword(plainPassword, storedHash) {
  return await bcrypt.compare(plainPassword, storedHash);
}

Putting it together in a signup/login flow:

// Signup
app.post('/signup', async (req, res) => {
  const { email, password } = req.body;
  const hash = await hashPassword(password);
  await db.users.create({ email, passwordHash: hash });
  res.status(201).send('Account created');
});

// Login
app.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await db.users.findByEmail(email);
  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    return res.status(401).send('Invalid credentials');
  }
  res.send('Logged in');
});

A couple of things worth knowing:

  • The salt is generated automatically and stored inside the returned hash string - you don't need a separate salt column in your database.

  • If a compiler/build toolchain is a problem for your deployment (e.g., some serverless or Alpine-based containers), the pure-JavaScript bcryptjs package is a drop-in alternative - about 30% slower, but no native build step required.

  • Bcrypt silently truncates anything past 72 bytes. If you allow long passwords, pre-hash with SHA-256 before passing it to bcrypt so nothing gets cut off unexpectedly.

Option 2: Argon2

Install the package:

npm install argon2

Hashing a password (on signup):

const argon2 = require('argon2');

// OWASP-recommended baseline configuration
const hashOptions = {
  type: argon2.argon2id,
  memoryCost: 19456, // 19 MiB - OWASP minimum
  timeCost: 2,        // iterations
  parallelism: 1,
};

async function hashPassword(plainPassword) {
  return await argon2.hash(plainPassword, hashOptions);
}

Verifying a password (on login):

async function verifyPassword(plainPassword, storedHash) {
  try {
    return await argon2.verify(storedHash, plainPassword);
  } catch {
    return false;
  }
}

Checking if an old hash needs upgrading (handy during a migration or after you raise your parameters):

async function needsRehash(storedHash) {
  return argon2.needsRehash(storedHash, hashOptions);
}

// During login, after a successful verify:
if (await needsRehash(user.passwordHash)) {
  const newHash = await hashPassword(plainPassword);
  await db.users.updatePasswordHash(user.id, newHash);
}

Like bcrypt, the salt and all the parameters (memory, time, parallelism) are embedded directly in the returned hash string, so verification always uses the exact settings the hash was created with - even if you change your defaults later.

Choosing your parameters: OWASP publishes a couple of acceptable profiles. A common starting point for a typical web server:

Profile

Memory

Time

Parallelism

Roughly

OWASP minimum

19 MiB

2

1

~40ms per hash

OWASP standard

64 MiB

3

1

~90–100ms per hash

High-security

128 MiB

4

2

~300ms+ per hash

Benchmark on your actual production hardware and pick the heaviest setting that still keeps login feeling instant - generally under 300–500ms. If you expect a lot of concurrent logins, remember to multiply your memory cost by your peak concurrent login count to make sure you're not going to run your server out of RAM.


The bottom line

  • New project? Reach for Argon2id. It's the current OWASP recommendation, it's memory-hard, and its parameters give you room to grow as hardware gets faster.

  • Existing bcrypt system running at cost 12+? You're not in danger - there's no need to rush a migration. Just make sure new signups eventually move over, using the rehash-on-login pattern.

  • Either way: never write your own hashing logic. Use a well-maintained library, keep your parameters aligned with current OWASP guidance, and revisit your settings every year or so as hardware evolves.

Password hashing isn't a "set it and forget it" decision - but it's one of the few security choices where getting it right the first time saves you a lot of pain later.

Tags:#NodeJs

Related Blogs

Related Blogs

We Created NestJs DuckDB: A Simple DuckDB Integration for NestJS
Programming
July 23, 2026

We Created NestJs DuckDB: A Simple DuckDB Integration for NestJS

As developers, we often use databases like PostgreSQL, MySQL, or MongoDB when building backend applications with NestJS. But not every data problem needs a traditional database setup.

The Hidden Dangers of SVG: Why Your "Safe" Image Format Can Hack Your Website
Cybersecurity
June 19, 2026

The Hidden Dangers of SVG: Why Your "Safe" Image Format Can Hack Your Website

SVG (Scalable Vector Graphics) is loved by developers and designers for its scalability, small file size, and crisp rendering. But behind its innocent XML-based appearance lies one of the most powerful vectors for Cross-Site Scripting (XSS) attacks.

Understanding .env in Node.js
DevOps
May 17, 2026

Understanding .env in Node.js

When building a Node.js app, you’ll often see a file called .env. At first, it may look confusing, but it’s actually one of the most important parts of managing your application properly.