skip to content

How do you derive a PBKDF2 hash of a password in Java using SecretKeyFactory and PBEKeySpec?

level: middleimportance: must knowfreq 70%

answer

  1. SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
  2. PBEKeySpec(password, salt, iterations, keyLengthBits)
  3. generateSecret(spec).getEncoded()
  4. keyLength is in BITS
  5. clearPassword() to wipe the char[]

basics

~10 s

Get a SecretKeyFactory for "PBKDF2WithHmacSHA256", build a PBEKeySpec from the password chars, a random salt, an iteration count, and a key length, then call generateSecret(spec).getEncoded() to get the hash bytes.

solid answer

~30 s

Java's built-in PBKDF2 lives in the JCA. You ask for a SecretKeyFactory with an algorithm like "PBKDF2WithHmacSHA256", then create a PBEKeySpec(password, salt, iterations, keyLengthBits). The PBEKeySpec carries all four inputs: the password as a char[], a per-user random salt (16+ bytes from SecureRandom), an iteration/work factor (tens of thousands+), and the desired derived-key length in bits. factory.generateSecret(spec).getEncoded() returns the raw derived bytes. You store salt + iterations + the hash so you can recompute and compare on login. Call spec.clearPassword() afterward to wipe the char[]. Use SHA256 (or SHA512), not the legacy PBKDF2WithHmacSHA1, and a fresh salt per password.

code

java · 17 lines
java
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;

byte[] salt = new byte[16];
new SecureRandom().nextBytes(salt);

char[] password = "s3cret".toCharArray();
PBEKeySpec spec = new PBEKeySpec(password, salt, 600_000, 256); // 256 BITS
try {
    SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    byte[] hash = f.generateSecret(spec).getEncoded();
    // store: algo + iterations + base64(salt) + base64(hash)
} finally {
    spec.clearPassword();
    java.util.Arrays.fill(password, '\0');
}

go deeper

for a junior

Knows you must hash, not store plaintext, and can name SecretKeyFactory + PBEKeySpec as the Java way to do PBKDF2.

for a middle

Can write the full call sequence, knows keyLength is in bits, uses a random per-user salt, and stores salt + iterations alongside the hash.

for a senior

Wipes the char[], compares in constant time, chooses SHA-256/512 over SHA-1, tunes the iteration count to hardware, and designs a self-describing stored format.

for a principal

Reasons about algorithm-agility (versioned hash format, transparent rehash-on-login), org-wide policy, FIPS/compliance constraints that may force PBKDF2, and migration strategy off legacy parameters.

## What problem this solves When you store user passwords, you must never store the plaintext. Instead you store a one-way transformation so that even if your database leaks, attackers can't trivially recover passwords. A plain hash (like a single SHA-256) is too fast: an attacker with the leaked hashes can try billions of guesses per second on a GPU. A **password-based key derivation function (KDF)** like **PBKDF2** deliberately makes each guess slow and adds a per-user random value (a **salt**), so attackers can't reuse work across users or precompute tables. ## The Java building blocks (JCA) Java's cryptography lives in the **JCA/JCE** (Java Cryptography Architecture / Extension). PBKDF2 is exposed through two classes: - **`SecretKeyFactory`** — a factory you obtain by *algorithm name*, e.g. `SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")`. The string names the PBKDF2 construction using HMAC-SHA-256 as its internal pseudo-random function (PRF). Other valid names: `PBKDF2WithHmacSHA512`, and the legacy `PBKDF2WithHmacSHA1` (avoid it). - **`PBEKeySpec`** — a *key specification* ("PBE" = password-based encryption) that bundles the four inputs PBKDF2 needs: `new PBEKeySpec(char[] password, byte[] salt, int iterationCount, int keyLength)`. Note `keyLength` is in **bits**, not bytes. ## The four inputs, defined 1. **password** — a `char[]`, deliberately not a `String`. `String`s are immutable and live in memory (and the string pool) until garbage-collected, so you can't reliably erase them. A `char[]` can be zeroed. 2. **salt** — random bytes unique per password, generated with `SecureRandom`. 16 bytes is standard. The salt is *not secret*; you store it alongside the hash. Its job is to make identical passwords hash differently and to defeat precomputed (rainbow) tables. 3. **iterationCount** — the **work factor**: how many times the internal PRF is applied. Higher = slower = more attacker cost. As of the mid-2020s, OWASP guidance is on the order of hundreds of thousands of iterations for SHA-256; you tune it to your hardware (aim for tens to ~100 ms per hash). 4. **keyLength** (bits) — how many output bits to produce; 256 (32 bytes) matched to the PRF is sensible. ## The call sequence ``` SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); PBEKeySpec spec = new PBEKeySpec(password, salt, 600_000, 256); byte[] hash = f.generateSecret(spec).getEncoded(); spec.clearPassword(); ``` `generateSecret` runs PBKDF2 and returns a `SecretKey`; `getEncoded()` extracts the raw derived bytes. `clearPassword()` wipes the internal copy of the char[]. ## What you persist You must store everything needed to recompute the hash at login time **except the password itself**: the **salt**, the **iteration count**, the **algorithm**, and the **derived hash**. A common format encodes them together, e.g. `algorithm$iterations$base64(salt)$base64(hash)`. Storing the iteration count lets you raise the work factor over time without breaking old hashes. ## Verification To check a login, read the stored salt + iterations, derive the hash of the entered password with the *same* parameters, and compare. Compare with a **constant-time** comparison (`MessageDigest.isEqual`), not `Arrays.equals` or `==`, to avoid timing side-channels (the deeper why is in the password-storage foundations topic). ## Checked exceptions `getInstance` throws `NoSuchAlgorithmException`; `generateSecret` throws `InvalidKeySpecException`. Both are checked; handle or wrap them. ## Why not just SHA-256? Because it's fast and unsalted by default — exactly the wrong properties. PBKDF2 = salt + tunable slowness, both built into the spec.

  • Why does PBEKeySpec take the password as a char[] rather than a String?
    A String is immutable and may linger in memory and the intern pool until GC, so it can't be reliably erased. A char[] can be overwritten (clearPassword / Arrays.fill) right after use, shrinking the window where the plaintext sits in memory.
  • What exactly do you store in the database after hashing?
    The salt, the iteration count, the algorithm identifier, and the derived hash bytes (typically base64-encoded and packed into one string). You never store the password. Storing iterations lets you upgrade the work factor later.

saying these in an interview costs you the question

  • Saying keyLength is in bytes (it's bits)
  • Passing the password as a String instead of char[]
  • Using a fixed/global salt instead of a per-user random one
  • Forgetting to store the iteration count and salt for later verification
  • Using PBKDF2WithHmacSHA1 for new code

context