What is Spring Security's PasswordEncoder and why should you use it instead of storing passwords directly?
answer
- encode() + matches(), never decrypt
- one-way, salted, slow
- leak-proof: store hash not plaintext
- DaoAuthenticationProvider calls matches()
- DelegatingPasswordEncoder is the recommended bean
basics
~20 sPasswordEncoder is an interface that turns a raw password into a one-way, salted hash. You store the hash, never the plaintext, so a database leak doesn't expose real passwords. Its matches() method verifies a login attempt.
solid answer
~40 sPasswordEncoder is Spring Security's SPI (service provider interface) for one-way password hashing. It has two core methods: encode(rawPassword) produces a hash to store, and matches(rawPassword, storedHash) checks a login attempt without ever reversing the hash. You store only the hash, so a leaked database doesn't reveal real passwords. Good encoders (BCrypt, Argon2) are deliberately slow and add a per-password random salt, which defeats rainbow tables and slows brute force. The recommended bean is PasswordEncoderFactories.createDelegatingPasswordEncoder(), which prefixes each hash with an algorithm id like {bcrypt}. DaoAuthenticationProvider uses this encoder automatically when authenticating a UserDetails, calling matches() rather than comparing strings, which also guards against timing attacks.
code
java · 11 lines@Bean
PasswordEncoder passwordEncoder() {
// {bcrypt}$2a$10$... — default DelegatingPasswordEncoder
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
// Registration
String hash = passwordEncoder.encode("s3cret"); // store this
// Login (Spring does this inside DaoAuthenticationProvider)
boolean ok = passwordEncoder.matches("s3cret", hash); // truego deeper
Know it's one-way hashing, you store the hash, and matches() verifies login.
Explain salting, why slow hashing matters, and that DaoAuthenticationProvider calls matches().
Discuss timing-safe comparison, non-deterministic encoding, and the DelegatingPasswordEncoder default bean.
Frame password storage as a security-lifecycle concern: algorithm agility, cost tuning, and breach containment.
## What PasswordEncoder is `org.springframework.security.crypto.password.PasswordEncoder` is an interface (an SPI — service provider interface) with three methods: - `String encode(CharSequence rawPassword)` — hashes a plaintext password into a storable string. - `boolean matches(CharSequence rawPassword, String encodedPassword)` — checks whether a raw password corresponds to a stored hash. It re-hashes the raw input (using the salt embedded in the stored hash) and compares; it never decrypts anything. - `default boolean upgradeEncoding(String encodedPassword)` — returns whether the stored hash should be re-encoded with stronger parameters (default `false`). ## Why hashing, not plaintext or encryption - **Plaintext**: a database leak instantly exposes every real password, and users reuse passwords across sites. - **Encryption** is reversible — if an attacker gets the key, they get every password. Passwords should be **one-way hashed** so there is no key to steal. - A proper password hash is **slow by design** (unlike SHA-256, which is fast and therefore weak for passwords) and includes a **random salt** per password so identical passwords produce different hashes, defeating precomputed rainbow tables. ## How it fits into Spring Security `DaoAuthenticationProvider` loads a `UserDetails` (whose `getPassword()` is the stored hash) and calls `passwordEncoder.matches(presentedPassword, storedHash)`. If it returns `true`, authentication succeeds. You never compare strings yourself; `matches()` is written to run in roughly constant time to reduce timing side-channels. ## The recommended bean Since Spring Security 5, the idiomatic bean is: ```java @Bean PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } ``` This returns a `DelegatingPasswordEncoder` whose default algorithm is BCrypt and whose stored hashes carry an `{id}` prefix such as `{bcrypt}$2a$10$...`. ## Common gotchas - **`NoOpPasswordEncoder`** stores plaintext and exists only for legacy tests/demos — it is `@Deprecated` and must never reach production. - If you store a hash with **no `{id}` prefix** while using a `DelegatingPasswordEncoder`, `matches()` throws `IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"`. - Encoding is **non-deterministic**: calling `encode("secret")` twice yields different strings (different salts), so you must verify with `matches()`, never by re-encoding and string-comparing.
- Why is SHA-256 a poor choice for hashing passwords even with a salt?SHA-256 is designed to be extremely fast, so an attacker can try billions of guesses per second on stolen hashes. Password hashes must be deliberately slow and tunable (BCrypt, Argon2, PBKDF2) to make brute force expensive.
- Why does encode() return a different value each time for the same password?Each call generates a fresh random salt embedded in the output, so identical passwords produce different hashes. That is why you verify with matches() (which reads the stored salt) rather than re-encoding and comparing strings.
saying these in an interview costs you the question
- Thinking PasswordEncoder encrypts (reversible) rather than hashes (one-way)
- Saying you verify a password by re-encoding it and comparing strings
- Using NoOpPasswordEncoder or plain SHA/MD5 in production
- Believing the same password always produces the same hash