Password encoders: BCrypt, Argon2. Why never plain-text o… — Cracked Java
// Spring Framework & Spring Boot · Spring Security Basics
MidTheoryBig Tech

Password encoders: BCrypt, Argon2. Why never plain-text or MD5.

Passwords must be stored as a slow, salted, one-way hash — never plain text, never MD5 or SHA-1. A PasswordEncoder does exactly that: encode() produces the stored hash, matches(raw, stored) verifies a login attempt. The choice of algorithm is the whole ballgame.

Why never plain text / MD5 / SHA-1

  • Plain text: one database leak and every account is compromised — and users reuse passwords across sites.
  • MD5 / SHA-1: these are fast general-purpose hashes. Fast is exactly wrong for passwords — a GPU computes billions of MD5s per second, so a stolen hash falls to brute force / rainbow tables almost instantly. They're also unsalted by default and cryptographically broken.

Password hashing wants the opposite: deliberately slow, memory-hard, salted so each guess is expensive and precomputation is useless.

BCryptPasswordEncoder

The pragmatic default. Built-in per-password salt, with a tunable strength (work factor, default 10 → 2¹⁰ rounds). Raise it as hardware improves:

PasswordEncoder encoder = new BCryptPasswordEncoder(12);
String hash = encoder.encode("s3cret");
// $2a$12$Q.../... — algorithm, cost, salt, and hash all embedded
boolean ok = encoder.matches("s3cret", hash);

Argon2PasswordEncoder

Winner of the Password Hashing Competition and the current best-in-class choice. It's memory-hard — you tune memory, iterations, and parallelism — which defeats GPU/ASIC attacks that bcrypt is more vulnerable to:

PasswordEncoder encoder = new Argon2PasswordEncoder(16, 32, 1, 1 << 14, 2);

DelegatingPasswordEncoder — use this

Don't hardcode one algorithm. The factory default returns a DelegatingPasswordEncoder that prefixes each hash with its scheme:

@Bean
PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
// stores: {bcrypt}$2a$10$...   or   {argon2}$argon2id$v=19$...

matches() reads the {id} prefix and routes to the right encoder, so old {bcrypt} hashes keep working while new ones are written with a stronger default — and you can transparently upgrade users on their next login.

Mark your status