{"record":{"id":"860b8e938fa45b53","repo":"spring-projects/spring-security","slug":"invalid-salt","errorCode":null,"errorMessage":"Invalid salt","messagePattern":"Invalid salt","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java","lineNumber":625,"sourceCode":"\t\tBCrypt B;\n\t\tString real_salt;\n\t\tbyte saltb[], hashed[];\n\t\tchar minor = (char) 0;\n\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) > '$') {","sourceCodeStart":607,"sourceCodeEnd":643,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java#L607-L643","documentation":"BCrypt.hashpw() validates that the salt string is at least 28 characters before parsing. A shorter string cannot contain the bcrypt modular format ($2a$NN$ + 22-char salt), so \"Invalid salt\" is thrown.","triggerScenarios":"Passing a truncated hash, a raw 22-character base64 salt without the $2a$ prefix, an empty string, or a plain-text value to hashpw as the salt parameter.","commonSituations":"Database column sized too small so stored hashes got truncated; slicing only the salt portion out of a stored hash then passing it back for verification; storing the result of gensalt incorrectly.","solutions":["Pass the FULL stored hash (including $2a$10$ prefix) as the salt when verifying","Store hashes in a column of at least 60 chars to avoid truncation","Use BCryptPasswordEncoder.matches(rawPassword, storedHash) rather than manual hashpw"],"exampleFix":"// before\nString hash = BCrypt.hashpw(pw, storedHash.substring(0, 22));\n// after\nString hash = BCrypt.hashpw(pw, storedHash); // full 60-char hash string","handlingStrategy":"validation","validationCode":"if (salt == null || salt.length() < 28 || !salt.startsWith(\"$2\")) {\n    throw new IllegalArgumentException(\"Not a valid bcrypt salt string\");\n}","typeGuard":"boolean isBcryptFormat(String s) {\n    return s != null && s.length() >= 28 && s.startsWith(\"$2\");\n}","tryCatchPattern":"try {\n    hash = BCrypt.hashpw(pw, salt);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().equals(\"Invalid salt\")) { /* treat as corrupt record / force reset */ }\n}","preventionTips":["Store full 60-char bcrypt hashes; size columns >= 60","Use BCryptPasswordEncoder.matches() which handles salt extraction","Never pass a bare base64 salt without its $2a$NN$ prefix"],"tags":["bcrypt","spring-security","salt","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-14T16:17:12.679Z"}