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 test
Expected 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.6 parses 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 by hash.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.
Hi!
argon2 0.6.0-rc.8can generate a valid PHC string with a non-default output length, butverifying that same PHC string via
Argon2::verify_passwordfails withPasswordInvalid.This looks like the
password-hash 0.6verification path rebuilds the hash from the PHC paramswithout preserving the encoded output length from the parsed PHC string.
Versions
argon2 = 0.6.0-rc.8password-hash = 0.6.0rustc 1.94.1Minimal repro
Cargo.tomlsrc/lib.rsReproduction steps
Expected behavior
Both tests pass. In particular, a PHC string generated by
argon2 0.6.0-rc.8withParams::new(8, 1, 1, Some(16))should verify successfully when parsed back and checked withverify_password.Actual behavior
The default-output-length test passes, but the non-default-output-length round trip fails with
PasswordInvalid.Verified locally with:
Notes
This also reproduces in a larger application when:
output_len = 16My working theory is:
password-hash 0.6parses the PHC string correctly.hash.params.Params::try_from(&hash.params)does not recover the encoded hash output length, becausethat length is carried by
hash.hash, not the params string.32) and returnsPasswordInvalid.I tested this locally from SQLPage while evaluating an upgrade to
argon2 0.6.0-rc.8.