Wiki / Concepts

Key Derivation

Turning something you have, a password or an existing secret, into a key of the right size and strength. Password KDFs are slow on purpose and salted per user; key-based KDFs are fast and exist to separate one secret into many.

Two families, and picking the wrong one costs everything
What are you starting from?
A password or PIN: low entropy, attacker-guessable
Argon2id, scrypt, bcrypt, PBKDF2: salted, deliberately slow
An existing high-entropy secret: DH output, master key
HKDF: fast, expands and separates by context
Running HKDF on a password gives no protection at all, because being fast is its whole design goal. Running PBKDF2 on a 256-bit Diffie-Hellman output wastes time and buys nothing, because the input was never guessable. The cost parameter only ever protects a secret an attacker could enumerate.

Key derivation is the step between a secret you have and a key you can use. It covers two jobs that look alike and behave nothing alike: making a guessable secret expensive to guess, and turning one strong secret into several independent keys.

Password-based derivation

A password has little entropy, so the only defence is to make each guess cost something. A key derivation function for passwords therefore takes a salt and a tunable work factor, and every serious one is built to resist the attacker's hardware rather than the defender's:

Function Cost parameters What it resists
PBKDF2 iteration count Nothing but raw repetition; tiny memory, so GPUs and ASICs parallelise it cheaply
bcrypt cost (log2 of rounds) Uses about 4 KB of state, which is awkward for GPUs; truncates the password at 72 bytes
scrypt N, r, p Memory-hard: needs 128 * N * r bytes, so parallel hardware pays in RAM
Argon2id m (memory), t (time), p (lanes) Memory-hard and side-channel aware; winner of the Password Hashing Competition

Current OWASP guidance is Argon2id with 19 MiB of memory, t=2, p=1; or scrypt with N=2^17, r=8, p=1; or bcrypt with cost 10 or more; or, when nothing better is available, PBKDF2-HMAC-SHA256 with 600,000 iterations. Those numbers are a snapshot, not a constant: the right work factor is the largest one your slowest legitimate device tolerates, measured rather than copied, and revisited as hardware improves.

import hashlib, os

salt = os.urandom(16)                       # unique per credential, stored in the clear
key = hashlib.pbkdf2_hmac("sha256", password, salt, 600_000, dklen=32)

# scrypt needs 128 * N * r bytes, so raise maxmem or it refuses:
key = hashlib.scrypt(password, salt=salt, n=2**17, r=8, p=1,
                     maxmem=2**28, dklen=32)

The maxmem detail is not trivia. hashlib.scrypt defaults to a 32 MiB limit, and the recommended parameters above need 128 MiB, so the call raises a ValueError that reads like a bug and is actually the memory-hardness doing its job.

Salts, and what they are for

A salt is a unique, random, non-secret value stored next to the derived key. It does not make one guess slower. It removes the attacker's ability to amortise: without it, one precomputed table covers every user, identical passwords produce identical hashes, and cracking one account cracks all of them. With a 16-byte salt per credential, every account has to be attacked on its own.

A pepper is a different thing: a secret value, held outside the database (in code, an environment variable, or an HSM), mixed in as well. It buys nothing against an attacker who has the whole host, and quite a lot against one who only dumped the database.

Wi-Fi is the textbook case of a bad salt choice. WPA2 derives the PMK as PBKDF2-HMAC-SHA1(passphrase, SSID, 4096 iterations, 32 bytes), using the network name as the salt. Names repeat across the world, so precomputed tables for common SSIDs are worth building, and 4096 iterations is far below anything acceptable today.

Key-based derivation

When the input is already a strong secret, the goal is separation rather than slowness. HKDF (RFC 5869) is the standard answer and it runs in two steps: extract condenses the input into a uniform pseudorandom key with Wiki: hmac, then expand stretches it into as many bytes as you need, mixing in an info string that names the purpose.

That info parameter is the point. Deriving enc-key and mac-key from the same master with different context strings guarantees that the two are independent, so a flaw that exposes one does not hand over the other. Reusing a single key for two purposes is a design bug that KDFs exist to prevent.

The same pattern appears in embedded fleets as key diversification: a device key derived as KDF(master_key, chip_uid), so every unit has its own key and a dumped device only burns itself. It works exactly as long as the master key stays off the devices; if the master is in every firmware image, the scheme is decoration. See Wiki: hardcoded-secrets.

From the attacker's side

The derived key is normally not the target. The password is, and the KDF only sets the price per guess:

  • Identify the function and its parameters. They are usually stored in the clear next to the hash, in a modular crypt string such as $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>.
  • The parameters tell you the cost per candidate, which tells you whether a wordlist run is minutes or years.
  • hashcat has a mode for each of these formats. Find yours in hashcat --help rather than trusting a number from a write-up, and remember that a captured WPA2 handshake is mode 22000.
  • No work factor rescues a weak secret. A four-digit PIN behind Argon2id is still 10,000 guesses. Raising the cost buys time proportionally, never exponentially, and entropy in the secret is the only term that scales.

Pitfalls

  • A plain hash for passwords. SHA-256 is fast by design; that is exactly the wrong property here. One GPU does billions per second.
  • A shared or missing salt. A constant salt in firmware is the same as no salt for anyone who has read the firmware.
  • Using the derived key as its own IV or nonce. Derive them separately, with different context strings.
  • Verifying with ==. Compare derived keys and tags in constant time, as with any Wiki: hmac verification.
  • Copying parameters from a tutorial. The right work factor depends on your hardware and your latency budget. Measure it, write down when you measured it, and raise it later.

Further reading