We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

This lesson's interactive features are locked, please to keep using them

Password KDFs

As we all know, passwords should never be stored in plaintext... but even hashing them isn't always enough.

If attackers steal your password hashes, they can test guesses offline on their own hardware with no rate limit. A fast general-purpose hash function like SHA-256 lets them rip through candidates, making SHA-256 a terrible password-storage choice.

Click to play video

Make Each Guess Expensive

A password key derivation function is designed to be expensive. For example, Argon2id consumes both CPU time and memory, making large batches of offline guesses much more costly.

import argon2 from "argon2";

export async function hashPassword(password: string): Promise<string> {
  return argon2.hash(password, {
    type: argon2.argon2id,
    memoryCost: 19 * 1024,
    timeCost: 2,
    parallelism: 1,
  });
}

Handling Legacy Hashes

Suppose your app already has users whose passwords are stored as 64-character SHA-256 hashes. Ruh-roh. Simply adding an Argon2-only verifier would lock them out!

You'll need to verify both formats during the migration, but create only Argon2id hashes for new or changed passwords:

// this regex matches exactly 64 hex characters
// meaning (in our system) it's a legacy SHA-256 hash
if (/^[a-f0-9]{64}$/i.test(passwordHash)) {
  return verifyLegacyPassword(password, passwordHash);
}

// this string starts with "$argon2id$" meaning
// the password was hashed with Argon2id
if (passwordHash.startsWith("$argon2id$")) {
  return argon2.verify(passwordHash, password);
}

return false;

Assignment

Bearly Secure stores passwords with fast SHA-256 hashes. Adopt Argon2id for every new or changed password without breaking existing legacy accounts.

Run and submit the CLI tests from the project root.