221 lines
8.1 KiB
Rust
221 lines
8.1 KiB
Rust
|
|
//! Test vectors for Argon2 password hashes in the PHC string format
|
||
|
|
//!
|
||
|
|
//! Adapted from: <https://github.com/P-H-C/phc-winner-argon2/blob/master/src/test.c>
|
||
|
|
|
||
|
|
#![cfg(all(feature = "alloc", feature = "password-hash"))]
|
||
|
|
|
||
|
|
use argon2::{
|
||
|
|
Algorithm, Argon2, AssociatedData, KeyId, ParamsBuilder, PasswordHash, PasswordHasher,
|
||
|
|
PasswordVerifier, Version,
|
||
|
|
};
|
||
|
|
use password_hash::{
|
||
|
|
errors::{Error, InvalidValue},
|
||
|
|
SaltString,
|
||
|
|
};
|
||
|
|
|
||
|
|
/// Valid password
|
||
|
|
pub const VALID_PASSWORD: &[u8] = b"password";
|
||
|
|
|
||
|
|
/// Invalid password
|
||
|
|
pub const INVALID_PASSWORD: &[u8] = b"sassword";
|
||
|
|
|
||
|
|
/// Password hashes for "password"
|
||
|
|
pub const VALID_PASSWORD_HASHES: &[&str] = &[
|
||
|
|
"$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQAAAAAAAAAAA$+r0d29hqEB0yasKr55ZgICsQGSkl0v0kgwhd+U3wyRo",
|
||
|
|
"$argon2id$v=19$m=262144,t=2,p=1$c29tZXNhbHQ$eP4eyR+zqlZX1y5xCFTkw9m5GYx0L5YWwvCFvtlbLow",
|
||
|
|
"$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc",
|
||
|
|
"$argon2d$v=19$m=65536,t=2,p=1$YzI5dFpYTmhiSFFBQUFBQUFBQUFBQQ$Jxy74cswY2mq9y+u+iJcJy8EqOp4t/C7DWDzGwGB3IM",
|
||
|
|
// Password with optional keyid
|
||
|
|
"$argon2d$v=19$m=65536,t=2,p=1,keyid=8PDw8A$YzI5dFpYTmhiSFFBQUFBQUFBQUFBQQ$Jxy74cswY2mq9y+u+iJcJy8EqOp4t/C7DWDzGwGB3IM",
|
||
|
|
// Password with optional data
|
||
|
|
"$argon2d$v=16$m=32,t=2,p=3,data=Dw8PDw8P$AAAAAAAAAAA$KnH4gniiaFnDvlA1xev3yovC4cnrrI6tnHOYtmja90o",
|
||
|
|
// Password with optional keyid&data
|
||
|
|
"$argon2d$v=16$m=32,t=2,p=3,keyid=8PDw8A,data=Dw8PDw8P$AAAAAAAAAAA$KnH4gniiaFnDvlA1xev3yovC4cnrrI6tnHOYtmja90o",
|
||
|
|
];
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn verifies_correct_password() {
|
||
|
|
for hash_string in VALID_PASSWORD_HASHES {
|
||
|
|
let hash = PasswordHash::new(hash_string).unwrap();
|
||
|
|
assert_eq!(
|
||
|
|
Argon2::default().verify_password(VALID_PASSWORD, &hash),
|
||
|
|
Ok(())
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn rejects_incorrect_password() {
|
||
|
|
for hash_string in VALID_PASSWORD_HASHES {
|
||
|
|
let hash = PasswordHash::new(hash_string).unwrap();
|
||
|
|
assert!(Argon2::default()
|
||
|
|
.verify_password(INVALID_PASSWORD, &hash)
|
||
|
|
.is_err());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Test PHC string format according to spec
|
||
|
|
// see: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
|
||
|
|
|
||
|
|
macro_rules! testcase_bad_encoding {
|
||
|
|
($name: ident, $err: expr, $hash: expr) => {
|
||
|
|
#[test]
|
||
|
|
fn $name() {
|
||
|
|
let hash = PasswordHash::new($hash).unwrap();
|
||
|
|
assert_eq!(
|
||
|
|
Argon2::default().verify_password(b"password", &hash),
|
||
|
|
Err($err)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
macro_rules! ignored_testcase_bad_encoding {
|
||
|
|
($name: ident, $err: expr, $hash: expr) => {
|
||
|
|
#[test]
|
||
|
|
#[ignore]
|
||
|
|
fn $name() {
|
||
|
|
let hash = PasswordHash::new($hash).unwrap();
|
||
|
|
assert_eq!(
|
||
|
|
Argon2::default().verify_password(b"password", &hash),
|
||
|
|
Err($err)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_invalid_version,
|
||
|
|
Error::Version,
|
||
|
|
"$argon2i$v=42$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_m_not_a_number,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::InvalidChar('d')),
|
||
|
|
"$argon2i$m=dummy,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_m_too_small,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::TooShort),
|
||
|
|
"$argon2i$m=0,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_m_too_big,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::InvalidFormat),
|
||
|
|
"$argon2i$m=4294967296,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_t_not_a_number,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::InvalidChar('d')),
|
||
|
|
"$argon2i$m=65536,t=dummy,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_t_too_small,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::TooShort),
|
||
|
|
"$argon2i$m=65536,t=0,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_t_too_big,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::InvalidFormat),
|
||
|
|
"$argon2i$m=65536,t=4294967296,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_p_not_a_number,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::InvalidChar('d')),
|
||
|
|
"$argon2i$m=65536,t=2,p=dummy$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_p_too_small,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::TooShort),
|
||
|
|
"$argon2i$m=65536,t=2,p=0$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
// TODO: Wrong error is returned, this should be changed in the `password-hash` crate
|
||
|
|
ignored_testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_p_too_big,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::InvalidFormat),
|
||
|
|
"$argon2i$m=65536,t=2,p=256$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_keyid_not_b64,
|
||
|
|
Error::B64Encoding(base64ct::Error::InvalidEncoding),
|
||
|
|
"$argon2i$m=65536,t=2,p=1,keyid=dummy$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_data_not_b64,
|
||
|
|
Error::B64Encoding(base64ct::Error::InvalidEncoding),
|
||
|
|
"$argon2i$m=65536,t=2,p=1,data=dummy$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
|
||
|
|
);
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn check_decoding_supports_out_of_order_parameters() {
|
||
|
|
// parameters order is not mandatory
|
||
|
|
let hash = "$argon2d$v=16$m=32,t=2,p=3,keyid=8PDw8A,data=Dw8PDw8P$AAAAAAAAAAA$KnH4gniiaFnDvlA1xev3yovC4cnrrI6tnHOYtmja90o";
|
||
|
|
let hash = PasswordHash::new(hash).unwrap();
|
||
|
|
assert!(Argon2::default()
|
||
|
|
.verify_password(b"password", &hash)
|
||
|
|
.is_ok());
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: Fix default parameters for decoding !
|
||
|
|
#[test]
|
||
|
|
#[ignore]
|
||
|
|
fn check_decoding_supports_default_parameters() {
|
||
|
|
// parameters order is not mandatory
|
||
|
|
let hash = "$argon2i$p=2,m=256,t=2$c29tZXNhbHQ$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs";
|
||
|
|
let hash = PasswordHash::new(hash).unwrap();
|
||
|
|
assert!(Argon2::default()
|
||
|
|
.verify_password(b"password", &hash)
|
||
|
|
.is_ok());
|
||
|
|
}
|
||
|
|
|
||
|
|
// m/t/p parameters are NOT optional according to spec
|
||
|
|
|
||
|
|
// TODO: Wrong error is returned, this should be changed in the `password-hash` crate
|
||
|
|
ignored_testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_missing_m,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::InvalidFormat),
|
||
|
|
"$argon2d$v=16$t=2,p=3$8PDw8PDw8PA$Xv5daH0zPuKO3c9tMBG/WOIUsDrPqq815/xyQTukNxY"
|
||
|
|
);
|
||
|
|
// TODO: Wrong error is returned, this should be changed in the `password-hash` crate
|
||
|
|
ignored_testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_missing_t,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::InvalidFormat),
|
||
|
|
"$argon2d$v=16$m=32,p=3$8PDw8PDw8PA$Xv5daH0zPuKO3c9tMBG/WOIUsDrPqq815/xyQTukNxY"
|
||
|
|
);
|
||
|
|
// TODO: Wrong error is returned, this should be changed in the `password-hash` crate
|
||
|
|
ignored_testcase_bad_encoding!(
|
||
|
|
argon2i_invalid_encoding_missing_p,
|
||
|
|
Error::ParamValueInvalid(InvalidValue::InvalidFormat),
|
||
|
|
"$argon2d$v=16$m=32,t=2$8PDw8PDw8PA$Xv5daH0zPuKO3c9tMBG/WOIUsDrPqq815/xyQTukNxY"
|
||
|
|
);
|
||
|
|
|
||
|
|
// Missing&invalid id/salt/hash fields is handled by `PasswordHash` so no need to test that here
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn check_hash_encoding_parameters_order() {
|
||
|
|
let params = ParamsBuilder::new()
|
||
|
|
.m_cost(32)
|
||
|
|
.t_cost(2)
|
||
|
|
.p_cost(3)
|
||
|
|
.data(AssociatedData::new(&[0x0f; 6]).unwrap())
|
||
|
|
.keyid(KeyId::new(&[0xf0; 4]).unwrap())
|
||
|
|
.build()
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
let ctx = Argon2::new(Algorithm::Argon2d, Version::V0x10, params);
|
||
|
|
|
||
|
|
let salt = vec![0; 8];
|
||
|
|
let password = b"password";
|
||
|
|
let salt_string = SaltString::encode_b64(&salt).unwrap();
|
||
|
|
let password_hash = ctx
|
||
|
|
.hash_password(password, &salt_string)
|
||
|
|
.unwrap()
|
||
|
|
.to_string();
|
||
|
|
|
||
|
|
// The parameters shall appear in the m,t,p,keyid,data order
|
||
|
|
assert_eq!(password_hash, "$argon2d$v=16$m=32,t=2,p=3,keyid=8PDw8A,data=Dw8PDw8P$AAAAAAAAAAA$KnH4gniiaFnDvlA1xev3yovC4cnrrI6tnHOYtmja90o");
|
||
|
|
}
|