Skip to content
pwnsy
cryptographyintermediate#kdf#pbkdf2#hkdf#argon2#key-derivation

Key Derivation Functions Explained

What key derivation functions do, how PBKDF2, HKDF, and Argon2 differ, and when to stretch a password versus derive keys from a strong secret.

Cryptographic keys have strict requirements. They must be the right length, evenly random across every bit, and unique to their purpose. The inputs we actually have rarely look like that. A password is short and predictable. A Diffie-Hellman shared secret is long but not uniformly distributed. A key derivation function is the machine that bridges the gap, taking a messy input and producing clean, usable keys.

The word covers two jobs that get confused constantly, and confusing them causes real weaknesses. This guide separates the two, walks through the main functions in each family, and shows which one to reach for. The single decision that drives every other choice is how much entropy the input already carries, so that is where the guide starts.

Two jobs that share a name

The phrase key derivation function is used for two related but distinct tasks, and the right tool depends entirely on which one you are doing.

The first job is stretching a weak secret. A human password has very little entropy; people pick short, common, guessable things. A password-based KDF takes that weak input and makes each guess expensive, so that even a determined attacker with the stored output can only test a limited number of candidates per second. The goal here is deliberate slowness.

The second job is deriving keys from a strong secret. When you already hold a high-entropy value, such as the shared secret from a key exchange, you often need to turn it into several purpose-specific keys, and you need them uniformly random. Here slowness would be pointless waste. The goal is a fast, clean transformation. HKDF is the standard tool, and its extract-and-expand design is why TLS 1.3 uses it in the key schedule described in How the TLS Handshake Works.

Using a fast KDF on a password leaves it easy to crack. Using a slow password KDF on a shared secret wastes resources and derives nothing extra in security. The first decision is always which job you are doing.

A concrete way to hold the distinction is to think about how many guesses an attacker gets per second. When the input is a password, the attacker who steals your stored output will try to guess the password by running candidates through the same function. Every unit of slowness you build in directly cuts their guess rate, so a function that takes a tenth of a second per attempt caps them near ten guesses per second per core instead of the billions a fast hash would allow. When the input is a random 256-bit secret, there is no guessing to slow down, because no attacker can enumerate a space that large. Spending a tenth of a second per derivation there buys nothing and only taxes your own throughput. The entropy of the input is what decides whether slowness is protection or waste.

A worked example: deriving keys the right way

Trace two realistic tasks to see the split in action.

In the first task you store user login credentials. A user picks the password "summer2024". That input carries very little entropy, so you feed it to Argon2id with a per-user random salt and a memory cost sized to your server budget, say tens of megabytes and a few iterations. The function returns a derived tag that you store alongside the salt and the parameters. At login you run the same function over the submitted password and compare. An attacker who steals the whole table faces a memory-hard function per guess, so a cheap dictionary of common passwords still takes real hardware and real time to grind, and the salt means they cannot attack all users at once with one precomputed table.

In the second task you have just completed an elliptic-curve Diffie-Hellman exchange and hold a shared secret. That value is high-entropy but not uniformly distributed, and it has structure a raw key should not have. You pass it through HKDF. The extract stage condenses it into one uniform pseudorandom key. The expand stage then produces, say, a 32-byte key labeled "client write" and another 32-byte key labeled "server write", each bound to its own context string. You never slow this down, because the input was already unguessable, and you never reuse one output key for two purposes, because the labels give you as many independent keys as you need. The same secret yields a clean, purpose-separated key set in microseconds.

The two tasks used different functions for a reason that traces straight back to the entropy of their inputs. Swap them and each one breaks in its own way.

Password-based KDFs: PBKDF2, scrypt, Argon2

These functions defend weak secrets by making each guess costly. All of them combine the password with a unique salt and apply a tunable cost, so identical passwords produce different outputs and every guess takes real work. The cost of these guesses is the whole defense, and it is covered further in Password Hashing: bcrypt, scrypt, Argon2.

PBKDF2 applies a keyed hash many times over, with the iteration count as its cost knob. It is old, standardized, and widely available. Its weakness is that its work is cheap to run in parallel, so attackers with GPUs or custom hardware can test enormous numbers of candidates at once. PBKDF2 is acceptable where compatibility demands it, with a high iteration count.

scrypt was designed to be memory-hard. It forces each guess to use a large block of memory, which raises the cost of building massively parallel cracking hardware, since memory is expensive to replicate. It resists GPU attacks far better than PBKDF2.

Argon2 is the modern choice and won the Password Hashing Competition. It is memory-hard and lets you tune memory, time, and parallelism independently. The Argon2id variant balances resistance to both GPU cracking and side-channel attacks, and is the recommended default for new systems.

KDFTypeCost knobsResists parallel hardwareUse for
PBKDF2Password stretchingIterationsWeaklyLegacy or compliance needs
scryptPassword stretchingMemory, CPUWellGood general choice
Argon2idPassword stretchingMemory, time, parallelismBestRecommended default
HKDFExtract-and-expandNone (fast by design)Not applicableHigh-entropy secrets
Do not stretch passwords with a plain hash

A single pass of SHA-256 or any fast hash is not a password KDF. Fast hashes are built to be fast, which is exactly what an attacker cracking a stolen database wants. Passwords need a deliberately slow, salted, memory-hard function such as Argon2id or scrypt. A fast hash with no salt and no cost factor is one of the most common storage mistakes there is.

Extract-and-expand: HKDF

HKDF solves the other job. You have a strong secret already, and you need well-formed keys from it. HKDF works in two clean stages.

The extract stage takes the input secret, which may be long and not uniformly random, and condenses it into a single fixed-length pseudorandom key. This smooths out any structure in the input so the result looks uniformly random. The expand stage then takes that key, plus a context label, and stretches it into as many output bytes as you need, split into separate keys for separate purposes.

That context label matters. By binding each derived key to a distinct label, such as one string for encryption and another for authentication, HKDF guarantees the keys are independent even though they came from the same secret. This is called domain separation, and it prevents a key meant for one purpose from being usable in another.

HKDF is fast on purpose. Its input is already high-entropy, so there is no weak secret to protect against guessing. Slowing it down would add cost with no security gain.

TLS 1.3 is the clearest real-world example. After the handshake computes a shared secret through its key exchange, that secret is not used directly. It flows through a key schedule built on HKDF, which extracts it into a master secret and then expands that into separate keys for the handshake and for application data, each bound to its own label. The same secret produces a set of independent, purpose-separated keys, which is precisely what extract-and-expand is for. NIST SP 800-108 describes the same family of pseudorandom-function-based derivation for deriving keys from an existing key.

Match the function to the input

The single most important choice is matching the KDF to the entropy of its input. A low-entropy input, like a password, needs a slow, salted, memory-hard KDF because the input itself is guessable. A high-entropy input, like a shared secret from a key exchange, needs a fast extract-and-expand KDF like HKDF because the input is already unguessable and only needs shaping. Getting this backward either leaves passwords crackable or wastes resources for nothing.

Salts, context, and cost

A few parameters carry most of the weight across both families.

A salt is a unique, random value combined with the input so that identical inputs produce different outputs. For password KDFs it defeats precomputed lookup tables and stops two users with the same password from sharing a hash. The salt does not need to be secret, only unique per derivation. Salts, and the related idea of a secret pepper, are covered in Salting and Peppering Explained. The salt must itself come from a secure generator, which is the concern of Secure Random Number Generation.

A context or info string in HKDF binds a derived key to its purpose, giving domain separation. A cost factor in a password KDF, whether iterations or memory, sets how expensive each guess is. Higher is safer up to what your servers can tolerate, and the right value is one you should raise over time as hardware gets faster.

Choosing correctly

The decision comes down to a short checklist.

  1. Identify the input. A human password is low entropy. A key exchange output or a random master key is high entropy. This determines everything.
  2. For passwords, use Argon2id. Fall back to scrypt, or to PBKDF2 with a high iteration count only where compatibility forces it. Always salt, always set a real cost.
  3. For high-entropy secrets, use HKDF. Derive separate keys with distinct context labels rather than reusing one key for multiple jobs.
  4. Never use a fast hash to protect a password, and never use a slow password KDF to shape a shared secret. Each mistake is a real weakness.
  5. Revisit cost factors periodically. What was expensive to crack a few years ago may be cheap now, so raise the memory or iteration cost as hardware improves.
  6. Rely on vetted implementations. Use a well-reviewed library rather than assembling a KDF by hand, and follow the parameter guidance in the OWASP Password Storage Cheat Sheet and NIST SP 800-108.

Because KDF weaknesses usually surface as crackable password stores or misused key material, staying current on exploited crypto flaws is worth the effort. Our Exploit Intelligence dashboard tracks which cryptographic library and authentication vulnerabilities are actually being exploited, so a KDF or hashing weakness in your dependencies gets prioritized on real activity.

Inside the two designs

It is worth seeing what these functions actually do, because the internal shape explains their properties.

PBKDF2 is a loop around a pseudorandom function, usually HMAC with an underlying hash. It feeds the password and salt in, then hashes the result again and again for the configured iteration count, chaining each round into the next. The output is the final chained value. Because every round is a cheap hash and each is independent of large memory, an attacker with thousands of parallel cores or a custom ASIC runs the whole loop many times over in parallel with little marginal cost. The iteration count raises the wall linearly, and hardware knocks it down faster than you can raise it. That is the structural reason PBKDF2 is the weakest of the password KDFs against dedicated cracking hardware, while remaining perfectly standardized and interoperable.

Argon2 is built around a large array of memory that it fills and then reads back in a data-dependent or data-independent pattern, depending on the variant. To compute the function you must hold that whole array, and to run many guesses in parallel you must hold many copies of it. Memory is expensive to replicate on custom silicon in a way that raw hashing is not, so the memory cost translates guessing throughput into a hardware bill an attacker cannot easily dodge. Argon2id runs a first pass in the data-independent mode that resists side-channel leakage, then switches to the data-dependent mode that maximizes resistance to time-memory trade-off attacks, which is why it is the recommended blend.

HKDF has no loop and no memory hardness because it is solving the other problem. Its extract step is a single HMAC over the input secret with the salt as the key, producing a fixed-length pseudorandom key. Its expand step is a short chain of HMAC calls, each fed the previous block, a counter, and the info label, concatenated until it has produced the requested number of output bytes. The whole thing is a handful of hash operations. It is fast because the input was already high-entropy and needed shaping rather than protecting.

Detection signals for a weak derivation

You can often tell a KDF is being misused from the outside or from a quick audit, and these signals are worth checking.

  • A password store that verifies instantly under load. If password checks return in well under a millisecond, the function is almost certainly a fast hash rather than a real password KDF. A correct password derivation is deliberately slow enough to feel it.
  • Identical stored outputs for identical passwords. If two users who happen to share a password have the same stored value, there is no per-user salt, which means precomputed tables and cross-user attacks both work.
  • No parameters stored beside the hash. A modern password hash string encodes its algorithm, salt, and cost parameters. A bare fixed-length hex string with none of that is a red flag for a hand-rolled scheme.
  • HKDF or a fast KDF applied to a password. In code review, a password flowing into HKDF, a single SHA-256, or a plain HMAC is the classic backwards choice: the input is guessable and the function does nothing to slow guessing.
  • One derived key reused for multiple purposes. When the same high-entropy secret produces a single key used for both encryption and authentication, the missing context labels mean the design skipped domain separation.
  • Cost factors frozen at a years-old value. An iteration or memory cost that has never been revisited since deployment is likely far too low for current hardware.
Store the parameters with the hash

A password hash should be self-describing. Record the algorithm, the salt, and every cost parameter alongside the derived value, ideally in the standard encoded format the library emits. That lets you verify old hashes, raise cost factors for new ones, and migrate users transparently as they log in. A bare hash with the parameters hard-coded elsewhere makes every future upgrade painful and error-prone.

KDF families at a glance

The functions divide cleanly once you sort them by the job they do.

FunctionFamilyDeliberately slowMemory-hardTypical use
PBKDF2Password stretchingYesNoCompliance and legacy interop
bcryptPassword stretchingYesMildlyEstablished password storage
scryptPassword stretchingYesYesGeneral password storage
Argon2idPassword stretchingYesYes (tunable)Recommended new default
HKDFExtract-and-expandNoNoKeys from a strong secret
NIST SP 800-108 KDFsKey-based derivationNoNoKeys from an existing key

The top block protects guessable inputs by being expensive. The bottom block shapes unguessable inputs and stays fast. Choosing across the line between the blocks is the mistake that matters. Choosing within a block is a matter of interoperability and tuning.

Common mistakes and misconceptions

"A salted SHA-256 is fine for passwords." A salt defeats precomputed tables, and it does nothing about speed. A fast hash still lets an attacker try billions of salted guesses per second. Passwords need a slow, memory-hard function on top of the salt.

"Encrypting the password is better than hashing it." Encryption is reversible by design, so a leaked key exposes every password at once. Password storage wants a one-way derivation that is never meant to be reversed, which is what a password KDF provides.

"HKDF makes any input safe." HKDF assumes its input already has enough entropy. Feed it a password and it produces a clean-looking key from a guessable secret, so the output is exactly as crackable as the password was. HKDF shapes entropy, it does not create it.

"A high iteration count on PBKDF2 is as good as Argon2." Raising iterations helps, and it only scales the part of the cost that parallel hardware handles cheaply. Argon2 and scrypt add memory hardness, which is the dimension that actually raises an attacker's hardware cost.

"The salt has to be secret." A salt only needs to be unique per derivation and unpredictable enough to prevent precomputation. It is stored in the clear next to the hash. The secret, optional, per-system value is a pepper, which is a different control.

How the field arrived at Argon2

The progression of password KDFs tracks the progression of cracking hardware. PBKDF2 came from an era when the main threat was a single machine running a dictionary, and iteration count was a sufficient lever against that. As graphics processors turned into general parallel compute, attackers gained thousands of cores that ran hash loops cheaply, and iteration-only functions lost ground. bcrypt held up better because its structure was awkward for that hardware, and scrypt introduced explicit memory hardness to attack the economics of parallelism directly. The Password Hashing Competition, which ran to select a modern standard, produced Argon2 as the winner, with independent tuning of memory, time, and parallelism so a defender can shape the cost curve against whatever hardware an attacker is likely to bring. Extract-and-expand derivation followed a separate track driven by protocol design, where HKDF was specified to give constructions like the TLS 1.3 key schedule a clean, analyzed way to turn one shared secret into many purpose-bound keys.

Frequently asked questions

What is the difference between a KDF and a hash function? A hash function is a general primitive that maps input to a fixed-length digest. A KDF is built for producing keys, and depending on its family it either deliberately slows down to protect a weak input or shapes a strong input into uniform, purpose-separated keys. Password KDFs are constructed from hash functions but add salt, cost, and often memory hardness.

Should I use HKDF to store passwords? No. HKDF is fast and assumes a high-entropy input, so it does nothing to slow down guessing a password. Use Argon2id, scrypt, or PBKDF2 with a high cost for passwords.

Is PBKDF2 still acceptable? Yes, where compatibility or compliance requires it, provided you use a high iteration count and a proper per-user salt. For new systems with a free choice, Argon2id is the stronger default because of its memory hardness.

Do I need a salt for HKDF? HKDF accepts an optional salt in its extract step, and it improves the analysis, and it is not the per-user anti-precomputation salt that password KDFs require. The two salts solve different problems even though they share a name.

How high should my cost factor be? High enough that a single derivation takes a noticeable fraction of a second on your production hardware, tuned to what your login volume can absorb, and revisited over time. There is no fixed number that stays correct, because hardware keeps getting faster.

What is domain separation and why does it matter? Domain separation means each derived key is bound to a distinct context label, so a key meant for encryption cannot be substituted for a key meant for authentication. HKDF provides it through the info parameter, and it prevents whole classes of key-reuse bugs.

A key derivation function is a small component with a large influence on whether the rest of your cryptography holds. Match the function to the entropy of its input, salt and cost your password derivations properly, and derive purpose-separated keys from strong secrets, and the keys you build everything else on start out clean.

Sources & further reading

Sharetwitterlinkedin

Related guides