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();
| Method | When 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.
| Scenario | Without Salt | With Salt |
| Two users with same password | Identical hashes — one breach exposes both | Different hashes — neither is revealed |
| Rainbow table attack | Instant lookup | Impossible — must brute-force with each unique salt |
| Dictionary attack | Pre-compute once, reuse | Must recompute for each target user |
| Salt storage | — | Store 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) | Algorithm | Iterations |
| Minimum | PBKDF2-SHA256 | 600,000 |
| Minimum | PBKDF2-SHA512 | 210,000 |
| Typical production | PBKDF2-SHA256 | 310,000 – 600,000 |
| Algorithm | Java support | Memory-hard | Notes |
| PBKDF2-SHA256 | Built-in JCA | No | NIST-approved, FIPS-compliant; vulnerable to GPU attacks |
| bcrypt | Third-party (jBCrypt) | Moderate | Max 72-byte password; widely used; not FIPS |
| Argon2id | Third-party (password4j, Bouncy Castle) | Yes | Winner of PHC 2015; most resistant to GPU/ASIC; OWASP first choice |
| scrypt | Bouncy Castle | Yes | Memory-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.