{"record":{"id":"8d05ffc885502ab7","repo":"spring-projects/spring-security","slug":"malformed-password-hash","errorCode":null,"errorMessage":"Malformed password hash","messagePattern":"Malformed password hash","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.java","lineNumber":136,"sourceCode":"\t\t\t\t.withParallelism(this.parallelism)\n\t\t\t\t.withMemoryAsKB(this.memory)\n\t\t\t\t.withIterations(this.iterations)\n\t\t\t\t.build();\n\t\t// @formatter:on\n\t\tArgon2BytesGenerator generator = new Argon2BytesGenerator();\n\t\tgenerator.init(params);\n\t\tgenerator.generateBytes(rawPassword.toString().toCharArray(), hash);\n\t\treturn Argon2EncodingUtils.encode(hash, params);\n\t}\n\n\t@Override\n\tprotected boolean matchesNonNull(String rawPassword, String encodedPassword) {\n\t\tArgon2EncodingUtils.Argon2Hash decoded;\n\t\ttry {\n\t\t\tdecoded = Argon2EncodingUtils.decode(encodedPassword);\n\t\t}\n\t\tcatch (IllegalArgumentException ex) {\n\t\t\tthis.logger.warn(\"Malformed password hash\", ex);\n\t\t\treturn false;\n\t\t}\n\t\tbyte[] hashBytes = new byte[decoded.getHash().length];\n\t\tArgon2BytesGenerator generator = new Argon2BytesGenerator();\n\t\tgenerator.init(decoded.getParameters());\n\t\tgenerator.generateBytes(rawPassword.toString().toCharArray(), hashBytes);\n\t\treturn constantTimeArrayEquals(decoded.getHash(), hashBytes);\n\t}\n\n\t@Override\n\tprotected boolean upgradeEncodingNonNull(String encodedPassword) {\n\t\tArgon2Parameters parameters = Argon2EncodingUtils.decode(encodedPassword).getParameters();\n\t\treturn parameters.getMemory() < this.memory || parameters.getIterations() < this.iterations;\n\t}\n\n\tprivate static boolean constantTimeArrayEquals(byte[] expected, byte[] actual) {\n\t\tif (expected.length != actual.length) {\n\t\t\treturn false;","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/crypto/src/main/java/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.java#L118-L154","documentation":"Argon2PasswordEncoder.matchesNonNull logs this warning when Argon2EncodingUtils.decode(encodedPassword) throws IllegalArgumentException, i.e. the stored encoded password is not a valid modular-crypt-format Argon2 hash. The encoder cannot extract the salt/parameters from the string, so it returns false instead of throwing — the match simply fails.","triggerScenarios":"Calling passwordEncoder.matches(rawPassword, encodedPassword) where encodedPassword was not produced by Argon2PasswordEncoder.encode (e.g. it is a plaintext value, a BCrypt/MD5 hash, a truncated hash, or a hash produced with a different variant like $argon2i instead of $argon2id).","commonSituations":"Migrating a legacy user database whose password column holds non-Argon2 hashes; a column truncated by a too-short VARCHAR; users whose password was stored plaintext; switching encoders without a DelegatingPasswordEncoder.","solutions":["Verify the stored hash starts with $argon2id$ (or the variant you configured) and is complete/untruncated.","Check the DB column length; Argon2 hashes are ~97+ chars, so widen the column if the hash was cut off.","If legacy hashes coexist, use DelegatingPasswordEncoder (PasswordEncoderFactories.createDelegatingPasswordEncoder) so each hash is decoded by the right encoder.","Re-encode the affected accounts' passwords (e.g. via a password-reset flow) so they are stored in Argon2 format."],"exampleFix":"// before\nboolean ok = new Argon2PasswordEncoder(16, 32, 1, 16384, 2).matches(raw, legacyBcryptHash);\n\n// after\nPasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();\nboolean ok = encoder.matches(raw, \"{bcrypt}\" + legacyBcryptHash);","handlingStrategy":"validation","validationCode":"boolean validArgon2Hash = encoded != null && encoded.matches(\"\\\\$argon2(id|i|d)\\\\$v=\\\\d+\\\\$m=\\\\d+,t=\\\\d+,p=\\\\d+\\\\$[A-Za-z0-9+/]+\\\\$[A-Za-z0-9+/]+\");\nif (!validArgon2Hash) { /* treat as no-match / migrate account */ }","typeGuard":null,"tryCatchPattern":"matches() never throws for malformed hashes (it returns false and logs); treat a persistent false as a signal to inspect/re-encode the stored hash rather than catching an exception.","preventionTips":["Store hashes only from passwordEncoder.encode(); never insert plaintext or other formats into the password column.","Size DB columns to at least 120 chars to avoid truncated Argon2 hashes.","Use DelegatingPasswordEncoder when multiple hash formats coexist.","Watch for this log warning in monitoring — repeated occurrences mean data, not user error."],"tags":["spring-security","argon2","password-encoding","authentication"],"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-23T08:17:48.524Z"}