java.util.Random and Math.random() are not suitable for cryptography — they are deterministic and predictable. Always use SecureRandom for security-sensitive values.

import java.security.SecureRandom; import java.util.HexFormat; // Default SecureRandom — uses OS entropy source (/dev/urandom on Linux) SecureRandom random = new SecureRandom(); // Generate a 16-byte random salt byte[] salt = new byte[16]; random.nextBytes(salt); System.out.println("Salt: " + HexFormat.of().formatHex(salt)); // Generate a random int in range [0, 100) int n = random.nextInt(100); // Generate a random long long token = random.nextLong(); // Generate a strong instance (may block on Linux until entropy is available) SecureRandom strong = SecureRandom.getInstanceStrong();
MethodWhen to use
new SecureRandom()Default — use for most cryptographic operations
SecureRandom.getInstanceStrong()Highest quality entropy; may block on low-entropy systems (avoid in containers at startup)
SecureRandom.getInstance("SHA1PRNG")Specific algorithm — rarely needed; prefer default
SecureRandom sr = new SecureRandom(); // 1. Generate a random session token (URL-safe Base64) byte[] tokenBytes = new byte[32]; sr.nextBytes(tokenBytes); String sessionToken = Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes); System.out.println("Token: " + sessionToken); // 43-character URL-safe string // 2. Generate a random password reset code (6-digit OTP) int otp = 100_000 + sr.nextInt(900_000); // always 6 digits System.out.println("OTP: " + otp); // 3. Generate a random UUID (v4 uses SecureRandom internally) java.util.UUID uuid = java.util.UUID.randomUUID(); // 4. Generate a random AES IV byte[] iv = new byte[12]; sr.nextBytes(iv); // 5. Generate a random salt for password hashing byte[] salt = new byte[16]; sr.nextBytes(salt);

Without a salt, identical passwords produce identical hashes — attackers can pre-compute hashes for common passwords (rainbow tables). A unique random salt per user prevents this.

ScenarioWithout SaltWith Salt
Two users with same passwordIdentical hashes — one breach exposes bothDifferent hashes — neither is revealed
Rainbow table attackInstant lookupImpossible — must brute-force with each unique salt
Dictionary attackPre-compute once, reuseMust recompute for each target user
Salt storageStore alongside hash — it's not a secret

PBKDF2 applies HMAC many thousands of times, making brute-force attacks slow. The iteration count is the primary cost parameter — increase it as hardware gets faster.

import javax.crypto.*; import javax.crypto.spec.*; import java.security.*; import java.util.Base64; public class Pbkdf2PasswordHash { private static final int ITERATIONS = 310_000; // OWASP 2023 for PBKDF2-SHA256 private static final int KEY_LENGTH = 256; // bits (32 bytes) private static final int SALT_LENGTH = 16; // bytes /** * Hash a password for storage. * Returns a storable string: "iterations:saltBase64:hashBase64" */ public static String hashPassword(char[] password) throws Exception { byte[] salt = new byte[SALT_LENGTH]; new SecureRandom().nextBytes(salt); byte[] hash = pbkdf2(password, salt, ITERATIONS, KEY_LENGTH); return ITERATIONS + ":" + Base64.getEncoder().encodeToString(salt) + ":" + Base64.getEncoder().encodeToString(hash); } /** * Verify a password against a stored hash string. */ public static boolean verifyPassword(char[] password, String stored) throws Exception { String[] parts = stored.split(":"); int iterations = Integer.parseInt(parts[0]); byte[] salt = Base64.getDecoder().decode(parts[1]); byte[] expected = Base64.getDecoder().decode(parts[2]); byte[] computed = pbkdf2(password, salt, iterations, expected.length * 8); return MessageDigest.isEqual(computed, expected); // constant-time } private static byte[] pbkdf2(char[] password, byte[] salt, int iterations, int keyLenBits) throws Exception { PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLenBits); try { SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); return skf.generateSecret(spec).getEncoded(); } finally { spec.clearPassword(); // wipe password from memory } } public static void main(String[] args) throws Exception { char[] password = "correct horse battery staple".toCharArray(); String stored = hashPassword(password); System.out.println("Stored: " + stored); System.out.println("Valid : " + verifyPassword(password, stored)); // true System.out.println("Invalid: " + verifyPassword("wrong".toCharArray(), stored)); // false } } Always call spec.clearPassword() in a finally block to zero out the password char array in memory. Using char[] instead of String for passwords lets you control when the data is cleared — String is immutable and stays in the heap until GC.

The iteration count should be calibrated so hashing takes ~100–300 ms on your login server. Use this benchmark to find the right value:

public static void benchmarkIterations() throws Exception { char[] password = "benchmark".toCharArray(); byte[] salt = new byte[16]; new SecureRandom().nextBytes(salt); int[] counts = {100_000, 200_000, 310_000, 500_000, 1_000_000}; for (int iterations : counts) { long start = System.currentTimeMillis(); PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, 256); SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec); spec.clearPassword(); long elapsed = System.currentTimeMillis() - start; System.out.printf("iterations=%,d time=%d ms%n", iterations, elapsed); } }
OWASP Recommendation (2023)AlgorithmIterations
MinimumPBKDF2-SHA256600,000
MinimumPBKDF2-SHA512210,000
Typical productionPBKDF2-SHA256310,000 – 600,000
AlgorithmJava supportMemory-hardNotes
PBKDF2-SHA256Built-in JCANoNIST-approved, FIPS-compliant; vulnerable to GPU attacks
bcryptThird-party (jBCrypt)ModerateMax 72-byte password; widely used; not FIPS
Argon2idThird-party (password4j, Bouncy Castle)YesWinner of PHC 2015; most resistant to GPU/ASIC; OWASP first choice
scryptBouncy CastleYesMemory-hard; complex parameter tuning

When FIPS compliance is not required and a third-party library is acceptable, prefer Argon2id. PBKDF2 is the right choice when you must stay within the JDK's built-in JCA providers.