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
- Ensure you pass a bcrypt-format string starting with $2a$, $2b$, $2x$, or $2y$
- Re-hash passwords stored under non-bcrypt schemes (or implement a legacy-verifier + rehash-on-login)
- 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
- Audit stored hashes for algorithm prefixes during migrations
- Normalize $2y$ (PHP) to $2a$ if your Java port rejects it
- Keep passwords under one hashing scheme or tag records with the algorithm used
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.