spring-projects/spring-security · error · IllegalArgumentException

Encoded password does not look like BCrypt: X

Error message

Encoded password does not look like BCrypt: X

What it means

upgradeEncodingNonNull re-checks an existing encoded password against the BCrypt pattern ($2a/$2y/$2b followed by rounds and a 53-char hash portion) before parsing its strength. If the stored value does not match the pattern, the library throws IllegalArgumentException, because it cannot determine the rounds of a non-BCrypt value.

Solutions

  1. Only call upgradeEncoding with values previously produced by BCryptPasswordEncoder (verify with a $2[aby]$ pattern first).
  2. Fix data migrations so the column contains full 60-char BCrypt hashes.
  3. Use DelegatingPasswordEncoder so non-BCrypt legacy hashes are handled by their own encoder/upgrade logic instead of this path.

Example fix

// before
if (encoder.upgradeEncoding(stored)) { ... } // throws on legacy hashes
// after
if (stored != null && stored.matches("\\$2[aby]\\$\\d{2}\\$.{53}") && encoder.upgradeEncoding(stored)) { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

private static final Pattern BCRYPT = Pattern.compile("\\A\\$2[aby]?\\$\\d{2}\\$[./A-Za-z0-9]{53}\\z");
boolean looksLikeBcrypt(String s) { return s != null && BCRYPT.matcher(s).matches(); }

Type guard

boolean isBcryptHash(String s) { return s != null && s.startsWith("$") && BCRYPT_PATTERN.matcher(s).matches(); }

Try / catch

try { needUpgrade = encoder.upgradeEncoding(stored); } catch (IllegalArgumentException e) { needUpgrade = true; // non-BCrypt legacy values always need re-encoding on next login }

Prevention

When it happens

Trigger: Calling encoder.upgradeEncoding(storedValue) with a value that is not a BCrypt hash — e.g. a plaintext password, a SHA-256 hex digest, a null-ish/empty string, or a hash produced by another PasswordEncoder — where BCRYPT_PATTERN.matcher(...).matches() fails.

Common situations: Migrating from another encoder (DelegatingPasswordEncoder with mixed stored formats); database column truncated so the hash is cut short; storing a password with whitespace or a wrong column's data.

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


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/7dad2f2e120f9bee. Report an issue: GitHub.

Appendix: source

Thrown at crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java:136

	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.
	 *
	 * @author Lin Feng
	 */
	public enum BCryptVersion {

		$2A("$2a"),

		$2Y("$2y"),

		$2B("$2b");

View on GitHub (pinned to 96852e8860)