Password Encoding
Password Encoding
Storing passwords as plaintext is one of the most destructive mistakes a developer can make. When a database is breached — and breaches happen to everyone — plaintext passwords give an attacker instant, complete access to every user account on your platform, often spilling across other services where users reused the same password. Spring Security's PasswordEncoder abstraction makes the right thing easy: you never see a raw password after registration, and the framework handles comparison safely for you.
The PasswordEncoder Contract
PasswordEncoder is a simple interface in org.springframework.security.crypto.password with three methods:
encode()— hashes the raw password. Call this exactly once: when the user sets or changes their password. Never call it again for the same value.matches()— takes the raw password from a login attempt and the stored hash, and returnstrueif they correspond. This is how Spring Security validates credentials.upgradeEncoding()— signals whether a stored hash should be re-encoded with the current algorithm (used byDelegatingPasswordEncoderduring phased migrations).
Why BCrypt Is the Default Choice
Spring Security ships with several implementations — MD5, SHA-256, PBKDF2, SCrypt, Argon2, and BCrypt — but BCryptPasswordEncoder remains the practical default for most applications because:
- Built-in salt: BCrypt generates a random 16-byte salt for every
encode()call and embeds it in the output string. Two calls with the same raw password produce different hashes — there is no need to manage salts yourself, and rainbow tables are useless. - Configurable work factor: The strength parameter (default 10) is an exponent: the algorithm performs 2strength iterations. Increasing it by 1 doubles the time. As hardware gets faster you can raise the factor and re-hash on next login.
- Fixed-length output: BCrypt always produces a 60-character string regardless of input length — safe to store in a
VARCHAR(60)column. - Constant-time comparison:
matches()does not short-circuit on the first differing byte, preventing timing attacks.
Registering BCryptPasswordEncoder as a Bean
Declare the encoder in a configuration class — typically alongside your SecurityFilterChain bean:
BCryptPasswordEncoder.upgradeEncoding() or a simple loop. Aim for 150–300 ms per hash — slow enough to frustrate brute-force attempts, fast enough to not affect login UX. Strength 10 was fine in 2010; 12 is a sensible minimum today.
Encoding on Registration
Inject PasswordEncoder into the service that creates users, and encode before persisting:
After save() the raw password is out of scope. Only the BCrypt hash is persisted. If your service receives the raw password via a DTO, clear or discard the DTO after encoding.
How Spring Security Uses the Encoder at Login
When a user submits login credentials, Spring Security's DaoAuthenticationProvider calls userDetailsService.loadUserByUsername() to fetch the stored hash, then calls passwordEncoder.matches(submittedPassword, storedHash). You do not write this matching logic — the provider handles it automatically as long as your PasswordEncoder bean and your UserDetailsService bean are registered in the same application context.
encode() again at login time. encode() generates a new random salt on every call, so comparing two encoded versions of the same password will almost always return false. Always call matches(rawInput, storedHash) for verification.
DelegatingPasswordEncoder — Future-Proofing Your Hashes
Real applications eventually need to migrate from one algorithm to another without forcing all users to reset their passwords. Spring Security's DelegatingPasswordEncoder stores a prefix alongside each hash that identifies the algorithm used:
Create it via the factory method rather than by hand:
This allows you to add a stronger algorithm later (e.g., Argon2) as the default for new registrations while still verifying old BCrypt hashes — upgradeEncoding() returns true for the old prefix so Spring re-hashes on successful login.
What About MD5 or SHA-256?
Both are general-purpose cryptographic hash functions, not password-hashing algorithms. They are designed to be fast — GPUs can compute billions of MD5 hashes per second. A modern GPU cluster can exhaust all common passwords in a leaked MD5 database in hours. BCrypt, SCrypt, and Argon2 are designed to be slow and memory-hard, making brute-force attacks economically impractical. Spring Security ships MessageDigestPasswordEncoder for MD5/SHA-256 but marks it @Deprecated — do not use it for new code.
Summary
Declare a BCryptPasswordEncoder bean (strength ≥ 12 for new projects), call encode() once at registration, let Spring Security's DaoAuthenticationProvider call matches() at login. Use DelegatingPasswordEncoder if you need a migration path between algorithms. Never store plaintext passwords, never use MD5 or SHA-256 for passwords, and never call encode() to verify a password. These rules are simple — the consequences of ignoring them are not.