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

  1. Confirm the stored value is a BCrypt hash: 60 chars, starts with $2a$, $2b$, or $2y$.
  2. If the value carries a prefix like {bcrypt}, switch to DelegatingPasswordEncoder and store it without passing raw prefixed strings to BCryptPasswordEncoder.
  3. 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).
  4. 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

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


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)