password encryption
BCrypt error
password hashing
security
programming error

Encoded password does not look like BCrypt

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The error saying an encoded password does not look like BCrypt usually means your application is trying to verify a password with a BCrypt-based encoder while the stored value is in some other format. That mismatch often appears during migrations, after framework upgrades, or when one password-writing code path forgot to use the shared encoder. The fix is not just swapping one class for another. You also need a clear policy for the hashes that are already in the database.

What Spring Security Is Expecting

BCryptPasswordEncoder expects a BCrypt-formatted hash. Those strings usually start with markers such as 2a2a, 2b2b, or a framework-specific prefix when a delegating encoder is in use.

java
1import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
2
3BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
4String hash = encoder.encode("s3cret");
5
6System.out.println(hash);
7System.out.println(encoder.matches("s3cret", hash));

If the value in the database is plain text, SHA-based, or stored with the wrong prefixing rules, the matcher has nothing valid to work with and fails immediately.

Audit the Data Before Changing Code

The most productive next step is usually a data audit. Find out how many rows are actually BCrypt, how many use a legacy format, and whether some records are plain text due to a broken write path.

sql
1SELECT id, password_hash
2FROM users
3WHERE password_hash NOT LIKE '$2a$%'
4  AND password_hash NOT LIKE '$2b$%'
5  AND password_hash NOT LIKE '{bcrypt}%';

That query gives you an estimate of the migration problem. Without it, teams often change encoder configuration blindly and still do not know whether the database is getting healthier or worse.

Prefer a Delegating Encoder for Mixed Environments

If your application may see more than one password format during a transition, DelegatingPasswordEncoder is the safer choice. It makes the algorithm explicit by storing a prefix with each value.

java
1import org.springframework.security.crypto.factory.PasswordEncoderFactories;
2import org.springframework.security.crypto.password.PasswordEncoder;
3
4PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
5String encoded = encoder.encode("s3cret");
6System.out.println(encoded);

That prefix-driven routing is much clearer than guessing which algorithm to apply based on string shape or historical assumptions.

Check Every Password-Writing Path

Many teams fix the login matcher and forget the real root cause: one flow is still saving passwords incorrectly. Audit all paths that create or update password hashes:

  • Registration.
  • Password reset.
  • Admin user creation.
  • Bulk imports.
  • Test fixtures and seed scripts.

Any one of those can keep injecting bad data into the table even after the login path is corrected. Security bugs persist when write paths are inconsistent.

Plan the Migration Strategy

There are usually two practical migration options:

  • Force resets for users with non-BCrypt values.
  • Verify legacy hashes temporarily, then re-hash with the current encoder after successful login.

The second option reduces friction, but it must be time-boxed. Temporary support has a habit of becoming permanent if nobody tracks the number of remaining legacy hashes. If you keep the fallback path, instrument it so you know exactly how many successful logins still depend on the legacy format.

A good migration plan includes metrics, deadlines, and one clear owner for removing the fallback path.

Centralize Encoder Configuration

Password handling becomes fragile when different components construct their own encoders. Put the password encoder in one configuration bean and inject it everywhere password values are written or checked.

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.security.crypto.factory.PasswordEncoderFactories;
4import org.springframework.security.crypto.password.PasswordEncoder;
5
6@Configuration
7public class SecurityConfig {
8    @Bean
9    PasswordEncoder passwordEncoder() {
10        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
11    }
12}

This makes policy changes visible and keeps test code aligned with production behavior.

Test the Whole Password Lifecycle

Unit tests are not enough here. You want integration tests that prove registration stores the expected format, login can verify it, and password reset does not bypass the encoder.

If a migration path exists, test that too. A password-migration bug is easy to miss because users do not all hit the same path on the same day. Good tests keep the old and new behavior explicit until the transition is complete.

Common Pitfalls

The biggest mistake is assuming the error is only a login issue when the real problem is bad stored data. Another is enabling temporary legacy support with no removal plan. Teams also get into trouble by using multiple encoder instances with different policies, trimming or altering stored hash strings in the database layer, or forgetting to audit all password-writing workflows after a migration.

Summary

  • The error means the stored password format does not match the active verifier.
  • Audit the database before changing encoder configuration.
  • Prefer a delegating encoder when multiple formats may exist during migration.
  • Check every code path that writes password hashes.
  • Treat legacy-format support as a migration phase with metrics and an end date.

Course illustration
Course illustration

All Rights Reserved.