spring-projects/spring-security · error · IllegalArgumentException

Invalid salt version

Error message

Invalid salt version

What it means

BCrypt.hashpw() validates the modular crypt format of the salt string before hashing. This IllegalArgumentException is a generic sentinel guard fired when the salt does not start with the two-character bcrypt prefix '$2' — i.e. the input is not a bcrypt-formatted salt (malformed, truncated, or a completely different hash format was passed as the salt). It rejects the input before any rounds parsing occurs; shorter invalid salts (<28 chars) or null salts get their own more specific messages.

Source

Thrown at crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java:629

		int rounds, off;
		StringBuilder rs = new StringBuilder();

		// Enforce max length for new passwords only
		if (!for_check && passwordb.length > 72) {
			throw new IllegalArgumentException("password cannot be more than 72 bytes");
		}
		if (salt == null) {
			throw new IllegalArgumentException("salt cannot be null");
		}

		int saltLength = salt.length();

		if (saltLength < 28) {
			throw new IllegalArgumentException("Invalid salt");
		}

		if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
			throw new IllegalArgumentException("Invalid salt version");
		}
		if (salt.charAt(2) == '$') {
			off = 3;
		}
		else {
			minor = salt.charAt(2);
			if ((minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b') || salt.charAt(3) != '$') {
				throw new IllegalArgumentException("Invalid salt revision");
			}
			off = 4;
		}

		// Extract number of rounds
		if (salt.charAt(off + 2) > '$') {
			throw new IllegalArgumentException("Missing salt rounds");
		}

		if (off == 4 && saltLength < 29) {

View on GitHub (pinned to 96852e8860)

Solutions

  1. Ensure you pass a bcrypt-format string starting with $2a$, $2b$, $2x$, or $2y$
  2. Re-hash passwords stored under non-bcrypt schemes (or implement a legacy-verifier + rehash-on-login)
  3. Check what the DB actually stores: SELECT the column and verify the prefix

Example fix

// before
String hash = BCrypt.hashpw(pw, legacySha256Hex);
// after
if (legacySha256Hex.startsWith("$2")) {
    String hash = BCrypt.hashpw(pw, legacySha256Hex);
} else {
    // verify with legacy scheme, then rehash with BCrypt.gensalt()
}
Defensive patterns

Strategy: validation

Validate before calling

if (!salt.matches("^\\$2[abxy]\\$\\d{2}\\$.{22}")) {
    throw new IllegalArgumentException("Not a recognized bcrypt salt version");
}

Type guard

boolean isKnownBcryptVersion(String s) {
    return s != null && s.length() > 3 && s.charAt(0) == '$' && s.charAt(1) == '2'
        && "abxy".indexOf(s.charAt(2)) >= 0;
}

Try / catch

try {
    hash = BCrypt.hashpw(pw, salt);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Invalid salt version")) { /* route to legacy verifier */ }
}

Prevention

When it happens

Trigger: Passing a salt/hash that does not begin with $2, e.g. an MD5/SHA hash, a plain salt, an empty-ish string that passed the length check but has another format, or a hash from a different scheme.

Common situations: Verifying a password against a hash migrated from another algorithm (sha256, md5crypt); a config value holding the wrong kind of hash; concatenated fields where a prefix was lost.

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/4a100df7e0264b2d. Report an issue: GitHub.