Articles

Key Derivation Functions Explained: HKDF vs PBKDF2

A key derivation function turns secret input into cryptographic keys. When to use HKDF for high-entropy secrets, and PBKDF2, scrypt, or Argon2 for passwords.

Chisato Chisato · · 5 min read
A red padlock icon over a keyboard

A key derivation function (KDF) takes some secret input and produces one or more cryptographic keys of a chosen length. It sits between “I have a secret” and “I need an AES key, an HMAC key, and an IV.” The important part is that there are two very different kinds of KDF, designed for two very different inputs. HKDF is built for secrets that are already random, like a Diffie-Hellman shared value. Password-based KDFs such as PBKDF2, scrypt, and Argon2 are built for secrets that are not, like a human-chosen password. Using the wrong one is a common and serious mistake.

Why not just hash the secret?

It is tempting to take SHA-256(secret) and call the output a key. That works poorly for several reasons:

  • Length is fixed. A single hash gives you one output size. Real protocols often need several keys of different lengths.
  • No domain separation. If you need an encryption key and a separate authentication key, hashing the same secret twice gives you the same value twice.
  • Uneven input. Some secrets, such as elliptic-curve shared points, are not uniformly random bit strings. They have structure, and a raw hash is not a rigorous way to extract the randomness they contain.
  • Too fast for passwords. A single hash is cheap, which is exactly what an attacker guessing passwords wants.

KDFs address those problems with well-analyzed constructions.

HKDF: extract, then expand

HKDF is an HMAC-based KDF with two stages. It assumes the input already contains plenty of entropy, even if it is not perfectly uniform.

  1. Extract. Compute PRK = HMAC(salt, input_key_material). This condenses the input into a fixed-length pseudorandom key. The salt is optional but recommended; it does not need to be secret.
  2. Expand. Feed the PRK, a context string called info, and the desired output length into an HMAC-based loop that produces as many bytes as you need.

The info parameter is what makes HKDF useful in practice. Derive an encryption key with info = "app v1 encryption" and an authentication key with info = "app v1 mac", and you get two independent keys from one shared secret. Changing the label changes the output completely.

HKDF is everywhere in modern protocols. The TLS 1.3 key schedule is built from HKDF operations, deriving handshake and traffic keys from the shared secret established during the TLS handshake. Messaging protocols and many key-wrapping schemes use it the same way. Because it is built on HMAC, its security rests on well-studied primitives.

What HKDF does not do is slow anything down. It runs in microseconds, which is correct for random secrets and wrong for passwords.

Password-based KDFs: deliberately expensive

A password has little entropy compared with a random key. An attacker who obtains a derived key or a stored hash can try billions of candidate passwords, run each through the KDF, and check for a match. The only defense is to make each guess expensive.

Password-based KDFs add two ingredients:

  • A salt. A unique random value per password, so identical passwords produce different outputs and precomputed tables are useless.
  • A work factor. A tunable cost that makes each derivation take meaningful time, and in newer designs meaningful memory.

The main options:

  • PBKDF2 applies HMAC repeatedly for a configurable iteration count. It is widely supported and standardized, but it uses very little memory, so GPUs and custom hardware can run many guesses in parallel cheaply.
  • scrypt is memory-hard: each derivation requires a large block of memory, which makes massively parallel cracking hardware much more expensive.
  • Argon2, specifically the Argon2id variant, is memory-hard with tunable time, memory, and parallelism parameters, and is the usual default for new systems.

These same functions double as password-storage algorithms. Our guide to password hashing with bcrypt and Argon2 covers the storage side; the difference here is that the output is used as a key, for example to encrypt a local vault, rather than stored for comparison.

HKDF vs password-based KDFs

HKDFPBKDF2 / scrypt / Argon2
InputHigh-entropy secretLow-entropy password
SpeedFast by designSlow by design
Memory costNegligibleLow (PBKDF2) to high (scrypt, Argon2)
SaltOptional, recommendedRequired, unique per password
Context labelsBuilt in via infoNot built in
Typical usesTLS key schedules, session keys, key hierarchiesPassword vaults, disk encryption, password storage
Main failure if misusedPasswords cracked quicklyNeedless latency on every request

Using them in Node.js

Node’s built-in crypto module exposes both kinds.

import { hkdfSync, pbkdf2Sync, randomBytes } from "node:crypto";

// HKDF: derive two independent 32-byte keys from a random shared secret
const sharedSecret = randomBytes(32);
const salt = randomBytes(16);
const encKey = Buffer.from(hkdfSync("sha256", sharedSecret, salt, "myapp encryption", 32));
const macKey = Buffer.from(hkdfSync("sha256", sharedSecret, salt, "myapp mac", 32));

// PBKDF2: derive a key from a password with a per-user salt
const pwSalt = randomBytes(16);
const iterations = 600_000; // tune to your hardware and latency budget
const vaultKey = pbkdf2Sync("correct horse battery staple", pwSalt, iterations, 32, "sha256");

Store the salt and the parameters alongside the encrypted data. You will need exactly the same inputs to derive the same key later. For new password-based designs, prefer a maintained Argon2id library over PBKDF2 when your platform allows it, and revisit the cost parameters periodically, since hardware keeps getting faster.

Common mistakes

  • Running HKDF on a password. HKDF is fast, so an attacker can test guesses at full speed. Passwords always need a password-based KDF.
  • Reusing a salt across users. It lets attackers crack many accounts with one pass.
  • Reusing the same derived key for multiple purposes. Use distinct info labels, or separate derivations, for encryption, authentication, and anything else.
  • Hard-coding secrets. A KDF is only as good as its input. Keep master secrets in a proper secrets management system.
  • Treating derivation as key management. KDFs create keys; rotating, wrapping, and storing them is a separate concern, often handled with envelope encryption.

The takeaway

A key derivation function turns secret input into usable keys, and the right one depends on the input. Use HKDF, with distinct context labels, when you start from a random high-entropy secret. Use a salted, deliberately expensive function, preferably Argon2id, when you start from a password. Never swap them, never reuse salts or derived keys across purposes, and keep the master secrets themselves well protected.

Chisato Chisato · · 5 min read

What Is a JWKS? JSON Web Key Sets Explained

A JWKS is a JSON document of public keys that lets any service verify JWT signatures. How JSON Web Key Sets work, the kid header, caching, and key rotation.

#Security #Authentication #Cryptography
Chisato Chisato · · 5 min read

What Is TOTP? How Time-Based One-Time Passwords Work

TOTP generates a new six-digit code every 30 seconds from a shared secret and the current time. How authenticator apps use it, and where it falls short.

#Security #Authentication #Cryptography
Chisato Chisato · · 4 min read

What Is a Rainbow Table? Password Attacks Explained

A rainbow table is a precomputed lookup of hash chains that speeds up cracking unsalted password hashes. How it works and why salting defeats it.

#Security #Cryptography #Authentication