spring-projects/spring-security · error · IllegalArgumentException

Invalid salt revision

Error message

Invalid salt revision

What it means

BCrypt.hashpw() parses the salt's minor revision character after the '$2' prefix (expected one of a, x, y, b followed by '$'). This IllegalArgumentException guard fires when the third character of the salt is not a recognized bcrypt revision — for example a salt of revision '$2$' without a minor letter, a corrupted hash, or a bcrypt variant unsupported by this implementation. The salt is rejected before cost-round extraction begins.

Source

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

			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) {
			throw new IllegalArgumentException("Invalid salt");
		}
		rounds = Integer.parseInt(salt.substring(off, off + 2));

		real_salt = salt.substring(off + 3, off + 25);
		saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);

		if (minor >= 'a') {

View on GitHub (pinned to 96852e8860)

Solutions

  1. Use salts generated by BCrypt.gensalt() (defaults to a valid revision like $2a$)
  2. Normalize known-compatible revisions ($2y$) to $2a$ before verification if interop with PHP is needed
  3. Inspect the salt string character-by-character; it must match $2[abxy]$

Example fix

// before
String hash = BCrypt.hashpw(pw, phpHash); // $2y$...
// after
String salt = phpHash.startsWith("$2y$") ? "$2a$" + phpHash.substring(4) : phpHash;
String hash = BCrypt.hashpw(pw, salt);
Defensive patterns

Strategy: validation

Validate before calling

if (!salt.matches("^\\$2[a-z]?\\$")) {
    throw new IllegalArgumentException("Malformed bcrypt revision segment");
}

Type guard

boolean hasValidRevision(String s) {
    return s.length() > 3 && "axby".indexOf(s.charAt(2)) >= 0 && s.charAt(3) == '$';
}

Try / catch

try {
    hash = BCrypt.hashpw(pw, salt);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Invalid salt revision")) { /* log the raw salt for inspection */ }
}

Prevention

When it happens

Trigger: Salt strings like $2c$..., $2z$..., $2$ with no minor, or a malformed string like $2a10$... where the '$' after the revision is missing.

Common situations: Hashes produced by non-OpenBSD bcrypt variants ($2y$ from PHP should be accepted; older Java ports reject it — this fork accepts a,b,x,y), hand-crafted or corrupted salt strings, string manipulation chopping a character.

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