{"record":{"id":"31de8ad0bea70927","repo":"spring-projects/spring-security","slug":"encoded-password-does-not-look-like-scrypt-encod","errorCode":null,"errorMessage":"Encoded password does not look like SCrypt: {encodedPassword}","messagePattern":"Encoded password does not look like SCrypt: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java","lineNumber":162,"sourceCode":"\t\treturn new SCryptPasswordEncoder(DEFAULT_CPU_COST, DEFAULT_MEMORY_COST, DEFAULT_PARALLELISM, DEFAULT_KEY_LENGTH,\n\t\t\t\tDEFAULT_SALT_LENGTH);\n\t}\n\n\t@Override\n\tprotected String encodeNonNullPassword(String rawPassword) {\n\t\treturn digest(rawPassword, this.saltGenerator.generateKey());\n\t}\n\n\t@Override\n\tprotected boolean matchesNonNull(String rawPassword, String encodedPassword) {\n\t\treturn decodeAndCheckMatches(rawPassword, encodedPassword);\n\t}\n\n\t@Override\n\tprotected boolean upgradeEncodingNonNull(String encodedPassword) {\n\t\tString[] parts = encodedPassword.split(\"\\\\$\");\n\t\tif (parts.length != 4) {\n\t\t\tthrow new IllegalArgumentException(\"Encoded password does not look like SCrypt: \" + encodedPassword);\n\t\t}\n\t\tlong params = Long.parseLong(parts[1], 16);\n\t\tint cpuCost = (int) Math.pow(2, params >> 16 & 0xffff);\n\t\tint memoryCost = (int) params >> 8 & 0xff;\n\t\tint parallelization = (int) params & 0xff;\n\t\treturn cpuCost < this.cpuCost || memoryCost < this.memoryCost || parallelization < this.parallelization;\n\t}\n\n\tprivate boolean decodeAndCheckMatches(CharSequence rawPassword, String encodedPassword) {\n\t\tString[] parts = encodedPassword.split(\"\\\\$\");\n\t\tif (parts.length != 4) {\n\t\t\treturn false;\n\t\t}\n\t\tlong params = Long.parseLong(parts[1], 16);\n\t\tbyte[] salt = decodePart(parts[2]);\n\t\tbyte[] derived = decodePart(parts[3]);\n\t\tint cpuCost = (int) Math.pow(2, params >> 16 & 0xffff);\n\t\tint memoryCost = (int) params >> 8 & 0xff;","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java#L144-L180","documentation":"Before deciding whether an existing hash should be re-hashed with stronger parameters, upgradeEncodingNonNull splits the stored SCrypt string on '$' and expects exactly 4 parts. A stored value that is not in scrypt's `$s0$params$salt$key` format cannot be parsed, so an IllegalArgumentException naming the offending encoded password is thrown.","triggerScenarios":"Calling `upgradeEncoding(encodedPassword)` (inherited flow invoking upgradeEncodingNonNull) with a hash that was not produced by SCryptPasswordEncoder — e.g. a bcrypt hash, a plain password, or a truncated/corrupted scrypt string.","commonSituations":"User table migrated from another PasswordEncoder (bcrypt/argon2) without prefix detection; hand-edited or truncated hash rows; column truncation losing part of the hash in the database.","solutions":["Only call upgradeEncoding for hashes known to be scrypt-encoded; check the prefix (e.g. startsWith(\"$e0801$\")) or keep the encoder type per user.","Wrap the upgradeEncoding call in try-catch for IllegalArgumentException and treat foreign formats as needing a full re-encode on next login.","Fix/restore corrupted hash rows in the data store."],"exampleFix":"// before\nboolean upgrade = encoder.upgradeEncoding(storedPassword);\n// after\nboolean upgrade = storedPassword.startsWith(\"$e0801$\") && encoder.upgradeEncoding(storedPassword);","handlingStrategy":"type-guard","validationCode":"boolean isScrypt = storedPassword != null && storedPassword.chars().filter(c -> c == '$').count() == 4\n        && storedPassword.startsWith(\"$\");","typeGuard":"boolean looksLikeScrypt(String encoded) {\n    return encoded != null && encoded.startsWith(\"$s\")\n        && encoded.split(\"\\\\$\").length == 4;\n}","tryCatchPattern":"boolean upgrade;\ntry {\n    upgrade = encoder.upgradeEncoding(encoded);\n} catch (IllegalArgumentException e) {\n    upgrade = true; // not scrypt: force re-encode on next login\n}","preventionTips":["Store which encoder produced each hash and dispatch accordingly.","Check the '$'-delimited format before calling upgradeEncoding.","Guard against DB column truncation of hashes."],"tags":["java","spring-security","password-hashing","format-validation","migration"],"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"}