Skip to content

Commit 9cc8a3a

Browse files
committed
[WIP] Use the phc crate
Companion PR to RustCrypto/traits#2116 The `phc` crate was recently extracted from the `password-hash` crate, implementing the Password Hashing Competition (PHC) string format for storing password hashes. This also factored apart the error types so there are separate ones for `phc::Error` and `password_hash::Error`, which is the primary source of changes in this PR.
1 parent c9efabb commit 9cc8a3a

22 files changed

Lines changed: 265 additions & 197 deletions

File tree

Cargo.lock

Lines changed: 18 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ exclude = ["benches", "fuzz"]
1515

1616
[profile.dev]
1717
opt-level = 2
18+
19+
[patch.crates-io]
20+
password-hash = { git = "https://github.com/RustCrypto/traits", branch = "password-hash/use-phc-crate" }
21+
phc = { git = "https://github.com/RustCrypto/formats" }

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The following code example shows how to verify a password when stored using one
2525
of many possible password hashing algorithms implemented in this repository.
2626

2727
```rust
28+
# fn main() -> Result<(), Box<dyn core::error::Error>> {
2829
use password_hash::{phc, PasswordVerifier};
2930

3031
use argon2::Argon2;
@@ -35,12 +36,20 @@ use scrypt::Scrypt;
3536
let hash_string = "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQAAAAAAAAAAA$+r0d29hqEB0yasKr55ZgICsQGSkl0v0kgwhd+U3wyRo";
3637
let input_password = "password";
3738

38-
let password_hash = phc::PasswordHash::new(&hash_string).expect("invalid password hash");
39+
let password_hash = phc::PasswordHash::new(&hash_string)?;
3940

4041
// Trait objects for algorithms to support
4142
let algs: &[&dyn PasswordVerifier<phc::PasswordHash>] = &[&Argon2::default(), &Pbkdf2, &Scrypt];
4243

43-
password_hash.verify_password(algs, input_password).expect("invalid password");
44+
for alg in algs {
45+
if alg.verify_password(input_password.as_ref(), &password_hash).is_ok() {
46+
return Ok(());
47+
}
48+
}
49+
50+
// If we get here, the password is invalid
51+
# Err(Box::new(password_hash::Error::PasswordInvalid))
52+
# }
4453
```
4554

4655
## Minimum Supported Rust Version (MSRV) Policy

argon2/Cargo.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,24 @@ blake2 = { version = "0.11.0-rc.3", default-features = false }
2222

2323
# optional dependencies
2424
rayon = { version = "1.7", optional = true }
25-
password-hash = { version = "0.6.0-rc.3", optional = true }
25+
password-hash = { version = "0.6.0-rc.3", optional = true, features = ["phc"] }
26+
phc = { version = "0.3.0-pre", optional = true, features = ["rand_core"] }
2627
zeroize = { version = "1", default-features = false, optional = true }
2728

2829
[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]
2930
cpufeatures = "0.2.17"
3031

3132
[dev-dependencies]
3233
hex-literal = "1"
33-
password-hash = { version = "0.6.0-rc.3", features = ["rand_core"] }
3434

3535
[features]
36-
default = ["alloc", "password-hash", "rand"]
36+
default = ["alloc", "getrandom", "simple"]
3737
alloc = ["password-hash?/alloc"]
3838
std = ["alloc", "base64ct/std"]
3939

40-
getrandom = ["simple", "password-hash/getrandom"]
40+
getrandom = ["simple", "phc/getrandom"]
4141
parallel = ["dep:rayon"]
42-
rand = ["password-hash?/rand_core"]
43-
simple = ["password-hash"]
42+
simple = ["password-hash", "phc"]
4443
zeroize = ["dep:zeroize"]
4544

4645
[lints.rust.unexpected_cfgs]

argon2/src/error.rs

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
33
use core::fmt;
44

5-
#[cfg(feature = "password-hash")]
6-
use {crate::Params, core::cmp::Ordering, password_hash::errors::InvalidValue};
7-
85
/// Result with argon2's [`Error`] type.
96
pub type Result<T> = core::result::Result<T, Error>;
107

@@ -97,29 +94,20 @@ impl From<base64ct::Error> for Error {
9794
impl From<Error> for password_hash::Error {
9895
fn from(err: Error) -> password_hash::Error {
9996
match err {
100-
Error::AdTooLong => InvalidValue::TooLong.param_error(),
97+
Error::AdTooLong
98+
| Error::KeyIdTooLong
99+
| Error::MemoryTooLittle
100+
| Error::SecretTooLong
101+
| Error::ThreadsTooFew
102+
| Error::ThreadsTooMany
103+
| Error::TimeTooSmall => password_hash::Error::ParamsInvalid,
101104
Error::AlgorithmInvalid => password_hash::Error::Algorithm,
102-
Error::B64Encoding(inner) => password_hash::Error::B64Encoding(inner),
103-
Error::KeyIdTooLong => InvalidValue::TooLong.param_error(),
104-
Error::MemoryTooLittle => InvalidValue::TooShort.param_error(),
105-
Error::MemoryTooMuch => InvalidValue::TooLong.param_error(),
106-
Error::PwdTooLong => password_hash::Error::Password,
107-
Error::OutputTooShort => password_hash::Error::OutputSize {
108-
provided: Ordering::Less,
109-
expected: Params::MIN_OUTPUT_LEN,
110-
},
111-
Error::OutputTooLong => password_hash::Error::OutputSize {
112-
provided: Ordering::Greater,
113-
expected: Params::MAX_OUTPUT_LEN,
114-
},
115-
Error::SaltTooShort => InvalidValue::TooShort.salt_error(),
116-
Error::SaltTooLong => InvalidValue::TooLong.salt_error(),
117-
Error::SecretTooLong => InvalidValue::TooLong.param_error(),
118-
Error::ThreadsTooFew => InvalidValue::TooShort.param_error(),
119-
Error::ThreadsTooMany => InvalidValue::TooLong.param_error(),
120-
Error::TimeTooSmall => InvalidValue::TooShort.param_error(),
105+
Error::B64Encoding(_) => password_hash::Error::EncodingInvalid,
106+
Error::MemoryTooMuch | Error::OutOfMemory => password_hash::Error::OutOfMemory,
107+
Error::PwdTooLong => password_hash::Error::PasswordInvalid,
108+
Error::OutputTooShort | Error::OutputTooLong => password_hash::Error::OutputSize,
109+
Error::SaltTooShort | Error::SaltTooLong => password_hash::Error::SaltInvalid,
121110
Error::VersionInvalid => password_hash::Error::Version,
122-
Error::OutOfMemory => password_hash::Error::OutOfMemory,
123111
}
124112
}
125113
}

argon2/src/lib.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -659,16 +659,20 @@ impl CustomizedPasswordHasher<PasswordHash> for Argon2<'_> {
659659
#[cfg(all(feature = "alloc", feature = "password-hash"))]
660660
impl PasswordHasher<PasswordHash> for Argon2<'_> {
661661
fn hash_password(&self, password: &[u8], salt: &[u8]) -> password_hash::Result<PasswordHash> {
662-
let salt = Salt::new(salt)?;
662+
let salt = Salt::new(salt).map_err(|_| password_hash::Error::SaltInvalid)?;
663663

664664
let output_len = self
665665
.params
666666
.output_len()
667667
.unwrap_or(Params::DEFAULT_OUTPUT_LEN);
668668

669-
let output = Output::init_with(output_len, |out| {
670-
Ok(self.hash_password_into(password, &salt, out)?)
671-
})?;
669+
let mut buffer = [0u8; Output::MAX_LENGTH];
670+
let out = buffer
671+
.get_mut(..output_len)
672+
.ok_or(password_hash::Error::OutputSize)?;
673+
674+
self.hash_password_into(password, &salt, out)?;
675+
let output = Output::new(out).map_err(|_| password_hash::Error::OutputSize)?;
672676

673677
Ok(PasswordHash {
674678
algorithm: self.algorithm.ident(),
@@ -713,12 +717,7 @@ mod tests {
713717
let res =
714718
argon2.hash_password_customized(EXAMPLE_PASSWORD, salt, None, None, Params::default());
715719

716-
assert_eq!(
717-
res,
718-
Err(password_hash::Error::SaltInvalid(
719-
password_hash::errors::InvalidValue::TooShort
720-
))
721-
);
720+
assert_eq!(res, Err(password_hash::Error::SaltInvalid));
722721
}
723722

724723
#[test]

argon2/src/params.rs

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,9 @@ impl FromStr for Params {
360360
type Err = password_hash::Error;
361361

362362
fn from_str(s: &str) -> password_hash::Result<Self> {
363-
Self::try_from(&ParamsString::from_str(s)?)
363+
let params_string =
364+
ParamsString::from_str(s).map_err(|_| password_hash::Error::ParamsInvalid)?;
365+
Self::try_from(&params_string)
364366
}
365367
}
366368

@@ -374,21 +376,43 @@ impl TryFrom<&ParamsString> for Params {
374376
for (ident, value) in params.iter() {
375377
match ident.as_str() {
376378
"m" => {
377-
builder.m_cost(value.decimal()?);
379+
builder.m_cost(
380+
value
381+
.decimal()
382+
.map_err(|_| password_hash::Error::ParamInvalid { name: "m" })?,
383+
);
378384
}
379385
"t" => {
380-
builder.t_cost(value.decimal()?);
386+
builder.t_cost(
387+
value
388+
.decimal()
389+
.map_err(|_| password_hash::Error::ParamInvalid { name: "t" })?,
390+
);
381391
}
382392
"p" => {
383-
builder.p_cost(value.decimal()?);
393+
builder.p_cost(
394+
value
395+
.decimal()
396+
.map_err(|_| password_hash::Error::ParamInvalid { name: "p" })?,
397+
);
384398
}
385399
"keyid" => {
386-
builder.keyid(value.as_str().parse()?);
400+
builder.keyid(
401+
value
402+
.as_str()
403+
.parse()
404+
.map_err(|_| password_hash::Error::ParamsInvalid)?,
405+
);
387406
}
388407
"data" => {
389-
builder.data(value.as_str().parse()?);
408+
builder.data(
409+
value
410+
.as_str()
411+
.parse()
412+
.map_err(|_| password_hash::Error::ParamsInvalid)?,
413+
);
390414
}
391-
_ => return Err(password_hash::Error::ParamNameInvalid),
415+
_ => return Err(password_hash::Error::ParamsInvalid),
392416
}
393417
}
394418

@@ -426,16 +450,27 @@ impl TryFrom<&Params> for ParamsString {
426450

427451
fn try_from(params: &Params) -> password_hash::Result<ParamsString> {
428452
let mut output = ParamsString::new();
429-
output.add_decimal("m", params.m_cost)?;
430-
output.add_decimal("t", params.t_cost)?;
431-
output.add_decimal("p", params.p_cost)?;
453+
454+
for (name, value) in [
455+
("m", params.m_cost),
456+
("t", params.t_cost),
457+
("p", params.p_cost),
458+
] {
459+
output
460+
.add_decimal(name, value)
461+
.map_err(|_| password_hash::Error::ParamInvalid { name })?;
462+
}
432463

433464
if !params.keyid.is_empty() {
434-
output.add_b64_bytes("keyid", params.keyid.as_bytes())?;
465+
output
466+
.add_b64_bytes("keyid", params.keyid.as_bytes())
467+
.map_err(|_| password_hash::Error::ParamInvalid { name: "keyid" })?;
435468
}
436469

437470
if !params.data.is_empty() {
438-
output.add_b64_bytes("data", params.data.as_bytes())?;
471+
output
472+
.add_b64_bytes("data", params.data.as_bytes())
473+
.map_err(|_| password_hash::Error::ParamInvalid { name: "keyid" })?;
439474
}
440475

441476
Ok(output)

argon2/tests/kat.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ fn reference_argon2i_v0x10_mismatching_hash() {
583583
.unwrap();
584584
assert_eq!(
585585
Argon2::default().verify_password(b"password", &hash),
586-
Err(password_hash::errors::Error::Password)
586+
Err(password_hash::Error::PasswordInvalid)
587587
);
588588
}
589589

@@ -716,7 +716,7 @@ fn reference_argon2i_v0x13_mismatching_hash() {
716716
.unwrap();
717717
assert_eq!(
718718
Argon2::default().verify_password(b"password", &hash),
719-
Err(password_hash::errors::Error::Password)
719+
Err(password_hash::Error::PasswordInvalid)
720720
);
721721
}
722722

0 commit comments

Comments
 (0)