{"record":{"id":"e5fd4bf24d8f304d","repo":"spring-projects/spring-security","slug":"invalid-encoded-argon2-hash","errorCode":null,"errorMessage":"Invalid encoded Argon2-hash","messagePattern":"Invalid encoded Argon2-hash","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2EncodingUtils.java","lineNumber":107,"sourceCode":"\t * {@code $argon2<T>[$v=<num>]$m=<num>,t=<num>,p=<num>$<bin>$<bin>}\n\t *\n\t * where {@code <T>} is either 'd', 'id', or 'i', {@code <num>} is a decimal integer\n\t * (positive, fits in an 'unsigned long'), and {@code <bin>} is Base64-encoded data\n\t * (no '=' padding characters, no newline or whitespace).\n\t *\n\t * The last two binary chunks (encoded in Base64) are, in that order, the salt and the\n\t * output. Both are required. The binary salt length and the output length must be in\n\t * the allowed ranges defined in argon2.h.\n\t * @param encodedHash the Argon2 hash string as described above\n\t * @return an {@link Argon2Hash} object containing the raw hash and the\n\t * {@link Argon2Parameters}.\n\t * @throws IllegalArgumentException if the encoded hash is malformed\n\t */\n\tstatic Argon2Hash decode(String encodedHash) throws IllegalArgumentException {\n\t\tArgon2Parameters.Builder paramsBuilder;\n\t\tString[] parts = encodedHash.split(\"\\\\$\");\n\t\tif (parts.length < 4) {\n\t\t\tthrow new IllegalArgumentException(\"Invalid encoded Argon2-hash\");\n\t\t}\n\t\tint currentPart = 1;\n\t\tparamsBuilder = switch (parts[currentPart++]) {\n\t\t\tcase \"argon2d\" -> new Argon2Parameters.Builder(Argon2Parameters.ARGON2_d);\n\t\t\tcase \"argon2i\" -> new Argon2Parameters.Builder(Argon2Parameters.ARGON2_i);\n\t\t\tcase \"argon2id\" -> new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id);\n\t\t\tdefault -> throw new IllegalArgumentException(\"Invalid algorithm type: \" + parts[1]);\n\t\t};\n\t\tif (parts[currentPart].startsWith(\"v=\")) {\n\t\t\tparamsBuilder.withVersion(Integer.parseInt(parts[currentPart].substring(2)));\n\t\t\tcurrentPart++;\n\t\t}\n\t\tString[] performanceParams = parts[currentPart++].split(\",\");\n\t\tif (performanceParams.length != 3) {\n\t\t\tthrow new IllegalArgumentException(\"Amount of performance parameters invalid\");\n\t\t}\n\t\tif (!performanceParams[0].startsWith(\"m=\")) {\n\t\t\tthrow new IllegalArgumentException(\"Invalid memory parameter\");","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2EncodingUtils.java#L89-L125","documentation":"Argon2EncodingUtils.decode() splits the PHC-style encoded hash on '$' and requires at least 4 segments (algorithm, params, salt, hash). If the string has fewer parts, it cannot possibly represent a valid Argon2 hash, so an IllegalArgumentException is thrown. This guards against corrupt or truncated hash strings before parameter parsing begins.","triggerScenarios":"Calling Argon2EncodingUtils.decode(encodedHash) with a string that splits into fewer than 4 '$'-separated parts — e.g. null-derived empty string, a truncated hash, a plain-text password, or a hash from another algorithm (like a bare bcrypt fragment).","commonSituations":"Database column truncated the stored hash; user-supplied hash pasted incorrectly; migrating from another hasher and feeding its output to Argon2's decoder; loading config where the hash env var is empty.","solutions":["Verify the stored hash is a complete Argon2 PHC string starting with '$argon2id$' (or argon2i/argon2d) and re-generate it with Argon2PasswordEncoder.encode() if not","Check the persistence layer (column length, trim-on-save) is not truncating the hash","Validate the hash format in your own code before calling decode()"],"exampleFix":"// before\nArgon2Hash hash = Argon2EncodingUtils.decode(storedValue);\n// after\nif (storedValue == null || !storedValue.matches(\"\\\\$argon2(id|i|d)\\\\$.+\")) {\n    throw new IllegalStateException(\"Stored value is not an Argon2 hash\");\n}\nArgon2Hash hash = Argon2EncodingUtils.decode(storedValue);","handlingStrategy":"validation","validationCode":"static boolean isArgon2Hash(String s) {\n    return s != null && s.matches(\"\\\\$argon2(id|i|d)\\\\$v=\\\\d+\\\\$m=\\\\d+,t=\\\\d+,p=\\\\d+\\\\$[A-Za-z0-9+/]+\\$[A-Za-z0-9+/]+\");\n}","typeGuard":"if (encodedHash == null || encodedHash.chars().filter(c -> c == '$').count() < 4) { throw new IllegalArgumentException(\"not a PHC Argon2 hash\"); }","tryCatchPattern":"try {\n    Argon2Hash h = Argon2EncodingUtils.decode(encodedHash);\n} catch (IllegalArgumentException e) {\n    throw new InvalidStoredHashException(\"Stored credential is not a valid Argon2 hash\", e);\n}","preventionTips":["Store hashes only via Argon2PasswordEncoder.encode()","Check DB column length (a full PHC Argon2 hash is ~97+ chars)","Regex-validate hashes at the ingestion boundary","Never paste hashes from other algorithms into Argon2 storage"],"tags":["argon2","password-hashing","input-validation","java","spring-security"],"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"}