-
Notifications
You must be signed in to change notification settings - Fork 239
password-hash 0.6 Argon2 verification fails for PHC hashes with non-default output lengths #2352
Description
Hi!
argon2 0.6.0-rc.8 can generate a valid PHC string with a non-default output length, but
verifying that same PHC string via Argon2::verify_password fails with PasswordInvalid.
This looks like the password-hash 0.6 verification path rebuilds the hash from the PHC params
without preserving the encoded output length from the parsed PHC string.
Versions
argon2 = 0.6.0-rc.8- transitive
password-hash = 0.6.0 - Rust toolchain:
rustc 1.94.1
Minimal repro
Cargo.toml
[package]
name = "argon2-output-len-repro"
version = "0.1.0"
edition = "2024"
[dependencies]
argon2 = "=0.6.0-rc.8"src/lib.rs
#[cfg(test)]
mod tests {
use argon2::{
Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version,
};
const PASSWORD: &[u8] = b"password";
const SALT: &[u8] = b"aaaaaaaa";
#[test]
fn default_output_len_round_trip_still_verifies() {
let hash = Argon2::default()
.hash_password_with_salt(PASSWORD, SALT)
.unwrap()
.to_string();
let parsed = PasswordHash::new(&hash).unwrap();
assert_eq!(Argon2::default().verify_password(PASSWORD, &parsed), Ok(()));
}
#[test]
fn non_default_output_len_round_trip_should_verify() {
let params = Params::new(8, 1, 1, Some(16)).unwrap();
let hash = Argon2::new(Algorithm::Argon2id, Version::V0x13, params)
.hash_password_with_salt(PASSWORD, SALT)
.unwrap()
.to_string();
let parsed = PasswordHash::new(&hash).unwrap();
assert_eq!(Argon2::default().verify_password(PASSWORD, &parsed), Ok(()));
}
}Reproduction steps
cd repro/argon2-output-len-repro
cargo testExpected behavior
Both tests pass. In particular, a PHC string generated by argon2 0.6.0-rc.8 with
Params::new(8, 1, 1, Some(16)) should verify successfully when parsed back and checked with
verify_password.
Actual behavior
The default-output-length test passes, but the non-default-output-length round trip fails with
PasswordInvalid.
Verified locally with:
running 2 tests
test tests::non_default_output_len_round_trip_should_verify ... FAILED
test tests::default_output_len_round_trip_still_verifies ... ok
---- tests::non_default_output_len_round_trip_should_verify stdout ----
thread 'tests::non_default_output_len_round_trip_should_verify' panicked at src/lib.rs:30:9:
assertion `left == right` failed
left: Err(PasswordInvalid)
right: Ok(())
Notes
This also reproduces in a larger application when:
- verifying a stored Argon2 PHC hash whose encoded hash length is shorter than the default 32 bytes
- verifying OIDC nonce hashes generated with
output_len = 16
My working theory is:
password-hash 0.6parses the PHC string correctly.- The generic verifier reconstructs the algorithm parameters from
hash.params. - Argon2
Params::try_from(&hash.params)does not recover the encoded hash output length, because
that length is carried byhash.hash, not the params string. - Verification recomputes the hash with the default output length (
32) and returns
PasswordInvalid.
I tested this locally from SQLPage while evaluating an upgrade to argon2 0.6.0-rc.8.