Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Key Derivation Functions: Argon2, scrypt

🔐 Used in: password storage, login systems, encrypted vaults, disk encryption, backup tools

✅ Essential anytime a password becomes a key.

A password is not a cryptographic key. It’s short, predictable, and attacker-friendly.

So when you see requirements like:

  • “derive an encryption key from a passphrase”
  • “store user passwords safely”
  • “turn a shared secret into multiple keys”

You need a Key Derivation Function (KDF)1.

This chapter focuses on the most important real-world case: password-based key derivation using Argon2id2 and scrypt3.

The Problem: Passwords Are Weak Secrets

If an attacker steals your password database (or an encrypted vault file), they don’t need to “hack your login”.

They can do offline guessing: try billions of password candidates on their own hardware until one works.

Your goal is simple: make each password guess expensive.

That’s what password KDFs do:

  • they turn one password guess into lots of CPU work
  • and (for Argon2/scrypt) lots of memory

Salt: The Non-Secret Value That Stops Mass Attacks

Every password hash / derived key must use a unique, random salt4.

Salt is:

  • public (stored next to the hash / ciphertext)
  • unique per user / per encrypted file
  • what prevents “same password ⇒ same output” across victims

Without salts, attackers can reuse work at scale.

What You Should Store (And What You Must Not)

For password storage, you store:

  • the algorithm + parameters (e.g. Argon2id time/memory settings)
  • the salt
  • the resulting password hash

You must never store:

  • the plaintext password
  • a fast hash of the password (like SHA-256(password))

Modern libraries typically serialize all of this into a single string (e.g. a PHC-style5 hash string).

Argon2id: the modern default

💡 Used in password managers, servers, vaults, modern security guidance. Memory-hard, tunable, and the recommended Argon2 variant today

My Crate Logo Crate used: argon2

Argon2id is designed to make password cracking expensive on GPUs/ASICs6 by forcing large memory usage per guess.

What you tune (conceptually):

  • memory (MiB)
  • time (iterations)
  • parallelism (lanes/threads)

Rule of thumb: tune it so verification is “noticeable but acceptable” on your server, then periodically raise cost over time.

🧪 Code Example: Argon2id (source code)

#![allow(unused)]
fn main() {
pub fn run_argon2id_example() {
    use argon2::Argon2;
    use argon2::password_hash::{
        PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng,
    };

    let password = b"correct horse battery staple";
    let salt = SaltString::generate(&mut OsRng);

    let argon2 = Argon2::default(); // Argon2id by default
    let hash = argon2.hash_password(password, &salt).unwrap().to_string();

    // Store `hash` in your DB. Later, verify by parsing the encoded string.
    let parsed = PasswordHash::new(&hash).unwrap();
    argon2.verify_password(password, &parsed).unwrap();

    println!("Argon2id hash: {hash}");
}
}

Output:

Argon2id hash: $argon2id$v=19$m=19456,t=2,p=1$2SgpVk7SNjbMVDerM8ObNw$fJLxxiOuQK02MAx/bBYCydPDRQtMpi+gcqeWIJHUgaQ
🚨 Critical rule
Do not invent your own parameters or string formats. Use the library’s encoded hash format and verify with the library.

🟢 Conclusion

If you’re building a new system that stores passwords, Argon2id is the default choice.

Scrypt: still strong, still common

💡 Used in older-but-still-secure systems, disk encryption formats, cryptocurrencies. Memory-hard, widely deployed

My Crate Logo Crate used: scrypt

Scrypt has the same high-level goal as Argon2: make brute-force expensive, especially on specialized hardware. It’s still a valid choice, especially when interoperability matters.

🧪 Code Example: scrypt (source code)

#![allow(unused)]
fn main() {
pub fn run_scrypt_example() {
    use scrypt::{Params, scrypt};

    let password = b"correct horse battery staple";
    let salt = b"unique salt"; // should be random and unique per user/file in real systems

    // N=2^15, r=8, p=1 is a common baseline; tune for your environment.
    // log_n=15 (N=32768), r=8 ⇒ ~32 MiB RAM/guess; p=1 keeps parallelism modest.
    let params = Params::new(15, 8, 1, 32).unwrap();

    let mut key = [0u8; 32];
    scrypt(password, salt, &params, &mut key).unwrap();

    println!("scrypt-derived key: {}", hex::encode(key));
}
}

Output:

scrypt-derived key: 1b1829d47138ed3fddf496d90bd8f4da1a06b9ad18b4be890aa2d10cd7079326
🚨 Critical rule
Store the salt and parameters alongside the ciphertext so you can derive the same key during decryption.

🟢 Conclusion

If you can’t use Argon2id for some reason, scrypt is the next best widely-supported option.

KDFs in Real Systems: Password → Encryption Key

If you derive a key from a password, it usually feeds into an AEAD (like AES-GCM or ChaCha20-Poly1305).

The secure pattern looks like this:

password + salt + KDF parameters → 32-byte key → AEAD encrypt/decrypt

What must be persisted:

  • salt + KDF parameters (public)
  • AEAD nonce (public, unique)
  • ciphertext + tag

If you lose the salt/params/nonce, you lose decryptability.

Argon2 vs scrypt vs PBKDF2

If you need…Use
Best default for new password storageArgon2id
Broad legacy interoperabilityscrypt
“Legacy-safe” baseline onlyPBKDF27
🚨 Critical rule
Password KDFs (Argon2/scrypt/PBKDF2) are for weak secrets. For deriving multiple keys from an already-strong secret, use HKDF8 instead.

🟢 Conclusion

Passwords are attacker-controlled inputs.

A proper KDF turns each password guess into a costly operation, making offline attacks dramatically harder.

Practical rule: use Argon2id by default, fall back to scrypt when needed, and treat parameter + salt storage as part of your cryptographic design.


  1. KDF: Key Derivation Function. Derives cryptographic keys from a secret (often with salt/context), producing the right-length key material and supporting key separation. More

  2. Argon2: Modern memory-hard password hashing and KDF (recommended variant: Argon2id). More

  3. scrypt: Memory-hard password-based KDF. More

  4. Salt: Non-secret random value used to ensure uniqueness and defeat precomputation. More

  5. PHC: Standard string format that encodes a password hash/KDF output plus its parameters and salt. More

  6. GPU/ASIC attacks: Password cracking on specialized hardware; memory-hard KDFs aim to make each guess expensive. More

  7. PBKDF2: Iterated HMAC-based password KDF (not memory-hard). More

  8. HKDF: Key expansion KDF for strong secrets (not passwords). More