skip to content

Password Hashing APIs (PBKDF2/SecretKeyFactory)

Password hashing in Java via SecretKeyFactory with PBKDF2 and a PBEKeySpec carrying salt, iteration count and key length, or a library binding for bcrypt, scrypt or Argon2. Interviewers ask why a plain SHA-256 of a password is wrong, and this is where you answer it.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

How should you generate the salt for PBKDF2 in Java, and why does the choice of random source matter?

level: juniorimportance: must knowfreq 50%

answer

  1. SecureRandom.nextBytes(new byte[16])
  2. Never java.util.Random / Math.random for salts
  3. Fresh salt per password, 16 bytes
  4. Salt is not secret - stored with the hash
  5. SecureRandom = CSPRNG, java.util.Random = predictable

basics

~20 s

Use java.security.SecureRandom to fill a fresh byte array (16 bytes) for each password. Don't use java.util.Random or Math.random - they're predictable. The salt is stored with the hash and doesn't need to be secret, just unique and random.

solid answer

~40 s

A salt is per-password random data that makes identical passwords hash differently and defeats precomputed rainbow tables. Generate it with java.security.SecureRandom, a cryptographically strong PRNG, by allocating a byte[] (16 bytes is standard) and calling nextBytes(salt). Never use java.util.Random or Math.random for this: they're fast but predictable PRNGs whose output can be reconstructed from a few samples, so an attacker could anticipate salts. Create a fresh salt for every password (and on every password change) - reusing one salt across users re-enables rainbow tables and reveals which users share a password. The salt is not secret; store it alongside the hash and iterations. On most platforms `new SecureRandom()` is fine; avoid forcing SHA1PRNG, and don't over-engineer with SecureRandom.getInstanceStrong() unless you specifically need a blocking strong source.

code

java · 10 lines
java
import java.security.SecureRandom;

// one shared, thread-safe instance is fine
private static final SecureRandom RNG = new SecureRandom();

byte[] newSalt() {
    byte[] salt = new byte[16]; // 128 bits
    RNG.nextBytes(salt);
    return salt;
}

go deeper

for a junior

Uses SecureRandom.nextBytes for a 16-byte salt and knows not to use Math.random/java.util.Random.

for a middle

Generates a fresh salt per password, stores it with the hash, and can explain rainbow-table defense.

for a senior

Explains CSPRNG vs statistical PRNG, reuses a SecureRandom instance, sizes the salt, and knows when getInstanceStrong is and isn't appropriate.

for a principal

Sets entropy/PRNG standards across services, considers blocking-entropy behavior in containers, and audits all random sources used for security material.

## What a salt is and why it exists A **salt** is a chunk of random bytes you mix into the hashing of each password. It solves two problems: 1. **Rainbow tables.** Attackers precompute huge tables mapping common passwords to their hashes. If everyone's password were hashed the same way with no salt, one table cracks everyone. A unique salt per password means the attacker would need a *separate* table per salt - infeasible. 2. **Identical passwords looking identical.** Without a salt, two users with the password `password123` get the *same* stored hash, leaking that fact. A per-user salt makes their stored hashes differ. Crucially, a salt is **not a secret**. It's stored in plaintext next to the hash. Its only requirements are: **unique per password** and **unpredictable/random**. ## Why the random *source* matters Computers generate "random" numbers with a **pseudo-random number generator (PRNG)** - an algorithm that produces a sequence from an internal state. There are two kinds: - **Statistical PRNGs** like `java.util.Random` (and `Math.random()`, which uses it). These are fast and "random enough" for games or sampling, but they're **predictable**: the algorithm is public and the state is small, so observing a little output lets an attacker compute past and future values. `java.util.Random` uses a 48-bit seed - tiny by crypto standards. - **Cryptographically secure PRNGs (CSPRNGs)** like **`java.security.SecureRandom`**. These are seeded from OS entropy (hardware noise, timing, etc.) and are designed so that even knowing lots of output gives no usable advantage in predicting more. For anything security-relevant - salts, tokens, keys, IVs, nonces - you **must** use a CSPRNG. If salts were predictable, an attacker could precompute against the likely salts, weakening the whole scheme. ## How to do it in Java ``` import java.security.SecureRandom; byte[] salt = new byte[16]; // 128 bits is standard new SecureRandom().nextBytes(salt); // fills with strong random bytes ``` Notes: - **Size:** 16 bytes (128 bits) is the common recommendation; bigger is fine, smaller risks collisions across many users. - **Freshness:** generate a new salt for *every* password and on *every* change. Never share one global salt. - **`new SecureRandom()`** is fine on modern JVMs/OSes - it picks a strong native provider. Avoid hardcoding the legacy `SHA1PRNG` algorithm. - **`SecureRandom.getInstanceStrong()`** returns a (possibly blocking) "strong" instance backed by a blocking entropy source on some systems; reserve it for long-lived keys, not high-volume salt generation where it can stall. - **Reuse the instance:** a single `SecureRandom` is thread-safe and seeding is the expensive part, so you can keep one around rather than constructing it in a tight loop. ## Storing the salt Because it's not secret, you store the salt right next to the hash and iteration count (e.g. base64 in a combined record). At verification you read it back and feed it into the `PBEKeySpec` to recompute the hash. ## The common mistakes - Using `Math.random()` / `new Random()` - predictable. - Reusing a single hardcoded salt for all users - re-enables rainbow tables and leaks shared passwords. - Deriving the salt from the username - it's then predictable and not unique on rename. - Making the salt too short (e.g. 4 bytes) - collisions across a large user base.

  • Does the salt need to be kept secret like the password?
    No. A salt's job is to be unique and unpredictable-per-password, not hidden. It's stored in plaintext alongside the hash and iteration count. Security comes from the one-way hash and work factor, not from hiding the salt.
  • Why is java.util.Random unsuitable for generating salts?
    It's a statistical PRNG with a small 48-bit seed and a public algorithm, so its output is predictable - an attacker can reconstruct the sequence from a few values. Predictable salts let attackers precompute attacks. SecureRandom is a CSPRNG seeded from OS entropy and is designed to be unpredictable.

saying these in an interview costs you the question

  • Using Math.random() or java.util.Random to make a salt
  • Reusing one global/hardcoded salt for all users
  • Deriving the salt from the username or a counter
  • Thinking the salt must be kept secret
  • Using a 2-4 byte salt

context

open as a page

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

level: middleimportance: must knowfreq 70%

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.

open as a page

After deriving a PBKDF2 hash, what do you store, and how do you verify a password on login?

level: middleimportance: must knowfreq 62%

basics

~20 s

Store the salt, iteration count, algorithm, and the hash bytes (not the password). On login, re-derive the hash from the entered password using the stored salt and iterations, then compare it to the stored hash with a constant-time check.

open as a page

PBKDF2 ships with the JDK, so why pull in a library for bcrypt, scrypt, or Argon2, and how do you use them on the JVM?

level: seniorimportance: should knowfreq 55%

basics

~20 s

PBKDF2 only stresses CPU, so it's cheap to crack on GPUs/ASICs. bcrypt, scrypt, and Argon2 are also memory-hard, raising attacker cost much more. The JDK has no built-in for them, so you add a library like Spring Security's PasswordEncoder, jBCrypt, or Bouncy Castle.

open as a page

What are the common correctness and security pitfalls when implementing PBKDF2 with SecretKeyFactory in Java?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Watch for: keyLength in bits not bytes, passing the password as char[] and wiping it, a fresh random salt per user, a high iteration count, handling NoSuchAlgorithmException/InvalidKeySpecException, and comparing hashes in constant time. Also store the parameters so you can upgrade later.

open as a page