{"record":{"id":"0d7cc81fec6f83ae","repo":"spring-projects/spring-security","slug":"salt-cannot-be-null","errorCode":null,"errorMessage":"salt cannot be null","messagePattern":"salt cannot be null","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java","lineNumber":619,"sourceCode":"\t */\n\tpublic static String hashpw(byte passwordb[], String salt) {\n\t\treturn hashpw(passwordb, salt, false);\n\t}\n\n\tprivate static String hashpw(byte passwordb[], String salt, boolean for_check) {\n\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\");","sourceCodeStart":601,"sourceCodeEnd":637,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java#L601-L637","documentation":"BCrypt.hashpw() throws \"salt cannot be null\" when the salt parameter (the modular salt string like $2a$10$...) is null. A null salt string cannot be parsed to extract the cost and raw salt needed for hashing.","triggerScenarios":"Passing null as the second argument of BCrypt.hashpw, or a password-encoder configured with a null salt value (Spring Security's BCryptPasswordEncoder internally handles salt, so this appears with direct BCrypt API use).","commonSituations":"Storing/reading the per-user salt in a database column that is null, or a helper method returning null salt on cache miss.","solutions":["Generate a salt with BCrypt.gensalt() when none exists instead of passing null","Null-check the salt before calling hashpw","When verifying, always pass the full stored hash string as the salt parameter"],"exampleFix":"// before\nString hash = BCrypt.hashpw(pw, storedSalt); // storedSalt may be null\n// after\nString salt = (storedSalt != null) ? storedSalt : BCrypt.gensalt();\nString hash = BCrypt.hashpw(pw, salt);","handlingStrategy":"type-guard","validationCode":"if (salt == null || salt.isEmpty()) {\n    salt = BCrypt.gensalt();\n}","typeGuard":"boolean hasSalt(String salt) { return salt != null && !salt.isEmpty(); }","tryCatchPattern":"try {\n    hash = BCrypt.hashpw(pw, salt);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"salt cannot be null\")) { hash = BCrypt.hashpw(pw, BCrypt.gensalt()); }\n}","preventionTips":["Null-check salts loaded from persistence before hashing","When verifying, pass the stored hash itself (it embeds the salt)"],"tags":["bcrypt","spring-security","null","salt"],"backgroundTag":"null-argument","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"}