spring-projects/spring-security · warning
Encoded password does not look like BCrypt
Error message
Encoded password does not look like BCrypt
What it means
BCryptPasswordEncoder.matchesNonNull logs this warning when the encoded password does not match the BCrypt pattern (roughly \$2(a|y|b)?\$\d{2}\$[./0-9A-Za-z]{53}). The string cannot be a BCrypt hash, so BCrypt.checkpw would throw; the encoder logs and returns false instead.
Solutions
- Confirm the stored value is a BCrypt hash: 60 chars, starts with $2a$, $2b$, or $2y$.
- If the value carries a prefix like {bcrypt}, switch to DelegatingPasswordEncoder and store it without passing raw prefixed strings to BCryptPasswordEncoder.
- Migrate legacy hashes: store them with an appropriate prefix via PasswordEncoderFactories, or re-encode via a login-time upgrade (PasswordEncoder.upgradeEncoding / re-encode on successful auth).
- Check the column length (CHAR(60) or larger) so hashes are not truncated.
Example fix
// before
BCryptPasswordEncoder enc = new BCryptPasswordEncoder();
boolean ok = enc.matches(raw, "{bcrypt}$2a$10$..."); // warns, always false
// after
PasswordEncoder enc = PasswordEncoderFactories.createDelegatingPasswordEncoder();
boolean ok = enc.matches(raw, "{bcrypt}$2a$10$..."); Defensive patterns
Strategy: validation
Validate before calling
private static final Pattern BCRYPT = Pattern.compile("\\A\\$2(a|y|b)?\\$\\d{2}\\$[./0-9A-Za-z]{53}\\z");
boolean looksLikeBcrypt = encoded != null && BCRYPT.matcher(encoded).matches(); Try / catch
BCrypt.checkpw can throw IllegalArgumentException on malformed input; if calling BCrypt directly, wrap it: try { return BCrypt.checkpw(raw, encoded); } catch (IllegalArgumentException e) { log.warn(...); return false; } Prevention
- Always create users through passwordEncoder.encode(rawPassword).
- Reserve CHAR(60)/VARCHAR(60+) for BCrypt columns to prevent truncation.
- Prefer DelegatingPasswordEncoder so format is carried in the {id} prefix and mismatches are explicit.
- On successful login, re-encode and persist if upgradeEncoding() is true to migrate legacy hashes.
When it happens
Trigger: Calling matches(rawPassword, encodedPassword) where encodedPassword is plaintext, a SHA/MD5 hex digest, an Argon2/PBKDF2 hash, or an empty/whitespace string — anything not matching the $2[aby]$ cost$ salt+hash layout.
Common situations: Users created before a migration to BCrypt whose hashes are SHA-1/MD5; plaintext credentials seeded into the database; hashes prefixed with an encoder id like {bcrypt} passed to BCryptPasswordEncoder directly; a DB column truncating the hash.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Malformed password hash
- AccountStatusUserDetailsChecker.disabled
- AccountStatusUserDetailsChecker.expired
- An Authentication object was not found in the…
- Authenticated principal required to operate with ACLs
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/d4ff63059c22ad75.
Report an issue: GitHub.
Appendix: source
Thrown at crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java:126
this.version = version;
this.strength = (strength == -1) ? 10 : strength;
this.random = (random != null) ? () -> random : SecureRandomHolder::getInstance;
}
@Override
protected String encodeNonNullPassword(String rawPassword) {
String salt = getSalt();
return BCrypt.hashpw(rawPassword.toString(), salt);
}
private String getSalt() {
return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random.get());
}
@Override
protected boolean matchesNonNull(String rawPassword, String encodedPassword) {
if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
this.logger.warn("Encoded password does not look like BCrypt");
return false;
}
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
}
@Override
protected boolean upgradeEncodingNonNull(String encodedPassword) {
Matcher matcher = this.BCRYPT_PATTERN.matcher(encodedPassword);
if (!matcher.matches()) {
throw new IllegalArgumentException("Encoded password does not look like BCrypt: " + encodedPassword);
}
int strength = Integer.parseInt(matcher.group(2));
return strength < this.strength;
}
/**
* Stores the default bcrypt version for use in configuration.
*View on GitHub (pinned to 96852e8860)