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
- Use salts generated by BCrypt.gensalt() (defaults to a valid revision like $2a$)
- Normalize known-compatible revisions ($2y$) to $2a$ before verification if interop with PHP is needed
- 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
- Never construct salt strings by hand; use BCrypt.gensalt()
- Check revision compatibility before migrating hashes between implementations
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.