{"record":{"id":"4a100df7e0264b2d","repo":"spring-projects/spring-security","slug":"invalid-salt-version","errorCode":null,"errorMessage":"Invalid salt version","messagePattern":"Invalid salt version","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java","lineNumber":629,"sourceCode":"\t\tint rounds, off;\n\t\tStringBuilder rs = new StringBuilder();\n\n\t\t// Enforce max length for new passwords only\n\t\tif (!for_check && passwordb.length > 72) {\n\t\t\tthrow new IllegalArgumentException(\"password cannot be more than 72 bytes\");\n\t\t}\n\t\tif (salt == null) {\n\t\t\tthrow new IllegalArgumentException(\"salt cannot be null\");\n\t\t}\n\n\t\tint saltLength = salt.length();\n\n\t\tif (saltLength < 28) {\n\t\t\tthrow new IllegalArgumentException(\"Invalid salt\");\n\t\t}\n\n\t\tif (salt.charAt(0) != '$' || salt.charAt(1) != '2') {\n\t\t\tthrow new IllegalArgumentException(\"Invalid salt version\");\n\t\t}\n\t\tif (salt.charAt(2) == '$') {\n\t\t\toff = 3;\n\t\t}\n\t\telse {\n\t\t\tminor = salt.charAt(2);\n\t\t\tif ((minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b') || salt.charAt(3) != '$') {\n\t\t\t\tthrow new IllegalArgumentException(\"Invalid salt revision\");\n\t\t\t}\n\t\t\toff = 4;\n\t\t}\n\n\t\t// Extract number of rounds\n\t\tif (salt.charAt(off + 2) > '$') {\n\t\t\tthrow new IllegalArgumentException(\"Missing salt rounds\");\n\t\t}\n\n\t\tif (off == 4 && saltLength < 29) {","sourceCodeStart":611,"sourceCodeEnd":647,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java#L611-L647","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nString hash = BCrypt.hashpw(pw, legacySha256Hex);\n// after\nif (legacySha256Hex.startsWith(\"$2\")) {\n    String hash = BCrypt.hashpw(pw, legacySha256Hex);\n} else {\n    // verify with legacy scheme, then rehash with BCrypt.gensalt()\n}","handlingStrategy":"validation","validationCode":"if (!salt.matches(\"^\\\\$2[abxy]\\\\$\\\\d{2}\\\\$.{22}\")) {\n    throw new IllegalArgumentException(\"Not a recognized bcrypt salt version\");\n}","typeGuard":"boolean isKnownBcryptVersion(String s) {\n    return s != null && s.length() > 3 && s.charAt(0) == '$' && s.charAt(1) == '2'\n        && \"abxy\".indexOf(s.charAt(2)) >= 0;\n}","tryCatchPattern":"try {\n    hash = BCrypt.hashpw(pw, salt);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Invalid salt version\")) { /* route to legacy verifier */ }\n}","preventionTips":["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"],"tags":["bcrypt","spring-security","salt","version","format"],"backgroundTag":"invalid-argument-format","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}