Skip to content

Commit aa324ea

Browse files
committed
pbkdf2: initial MCF (Customized)PasswordHasher support
Initial support for computing password hash strings in Modular Crypt Format (MCF) instead of the PHC string format. Tested against a vector from passlib: https://passlib.readthedocs.io/en/stable/lib/passlib.hash.pbkdf2_digest.html
1 parent 62dd4ad commit aa324ea

16 files changed

Lines changed: 319 additions & 156 deletions

File tree

.readme/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ publish = false
88
[dependencies]
99
password-hash = "0.6.0-rc.3"
1010
argon2 = { path = "../argon2" }
11-
pbkdf2 = { path = "../pbkdf2", features = ["password-hash"] }
11+
pbkdf2 = { path = "../pbkdf2", features = ["phc"] }
1212
scrypt = { path = "../scrypt", features = ["phc"] }

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ opt-level = 2
2020
argon2 = { path = "./argon2" }
2121
pbkdf2 = { path = "./pbkdf2" }
2222
scrypt = { path = "./scrypt" }
23+
24+
password-hash = { git = "https://github.com/RustCrypto/traits" }

password-auth/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ password-hash = { version = "0.6.0-rc.7", features = ["alloc", "getrandom", "phc
2222

2323
# optional dependencies
2424
argon2 = { version = "0.6.0-rc.5", optional = true, default-features = false, features = ["alloc", "password-hash"] }
25-
pbkdf2 = { version = "0.13.0-rc.5", optional = true, default-features = false, features = ["password-hash"] }
25+
pbkdf2 = { version = "0.13.0-rc.5", optional = true, default-features = false, features = ["phc"] }
2626
scrypt = { version = "0.12.0-rc.6", optional = true, default-features = false, features = ["phc"] }
2727

2828
[features]

password-auth/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ pub fn is_hash_obsolete(hash: &str) -> Result<bool, ParseError> {
122122
|| hash.params != default_params_string::<scrypt::Params>());
123123

124124
#[cfg(feature = "pbkdf2")]
125-
return Ok(hash.algorithm != *pbkdf2::Algorithm::default().ident()
125+
return Ok(hash.algorithm != pbkdf2::Algorithm::default().into()
126126
|| hash.params != default_params_string::<pbkdf2::Params>());
127127

128128
Ok(true)

pbkdf2/Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ digest = { version = "0.11.0-rc.4", features = ["mac"] }
1818

1919
# optional dependencies
2020
hmac = { version = "0.13.0-rc.3", default-features = false, optional = true }
21-
password-hash = { version = "0.6.0-rc.7", default-features = false, optional = true, features = ["phc"] }
21+
mcf = { version = "0.6.0-rc.2", optional = true }
22+
password-hash = { version = "0.6.0-rc.7", default-features = false, optional = true }
2223
sha1 = { version = "0.11.0-rc.3", default-features = false, optional = true }
2324
sha2 = { version = "0.11.0-rc.3", default-features = false, optional = true }
2425

@@ -32,8 +33,9 @@ belt-hash = "0.2.0-rc.3"
3233

3334
[features]
3435
default = ["hmac"]
35-
getrandom = ["password-hash", "password-hash/getrandom"]
36-
password-hash = ["hmac", "dep:password-hash", "sha2"]
36+
getrandom = ["password-hash/getrandom"]
37+
mcf = ["hmac", "password-hash/alloc", "dep:mcf", "sha2"]
38+
phc = ["hmac", "password-hash/phc", "sha2"]
3739
rand_core = ["password-hash/rand_core"]
3840

3941
[package.metadata.docs.rs]

pbkdf2/src/algorithm.rs

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ use core::{
22
fmt::{self, Display},
33
str::FromStr,
44
};
5-
use password_hash::{Error, phc::Ident};
5+
use password_hash::Error;
6+
7+
#[cfg(feature = "phc")]
8+
use password_hash::phc::Ident;
69

710
/// PBKDF2 variants.
811
///
@@ -24,13 +27,25 @@ pub enum Algorithm {
2427
impl Algorithm {
2528
/// PBKDF2 (SHA-1) algorithm identifier
2629
#[cfg(feature = "sha1")]
27-
pub const PBKDF2_SHA1_IDENT: Ident = Ident::new_unwrap("pbkdf2");
30+
pub const PBKDF2_SHA1_ID: &'static str = "pbkdf2";
2831

2932
/// PBKDF2 (SHA-256) algorithm identifier
30-
pub const PBKDF2_SHA256_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha256");
33+
pub const PBKDF2_SHA256_ID: &'static str = "pbkdf2-sha256";
3134

3235
/// PBKDF2 (SHA-512) algorithm identifier
33-
pub const PBKDF2_SHA512_IDENT: Ident = Ident::new_unwrap("pbkdf2-sha512");
36+
pub const PBKDF2_SHA512_ID: &'static str = "pbkdf2-sha512";
37+
38+
/// PBKDF2 (SHA-1) algorithm identifier
39+
#[cfg(all(feature = "phc", feature = "sha1"))]
40+
pub(crate) const PBKDF2_SHA1_IDENT: Ident = Ident::new_unwrap(Self::PBKDF2_SHA1_ID);
41+
42+
/// PBKDF2 (SHA-256) algorithm identifier
43+
#[cfg(feature = "phc")]
44+
pub(crate) const PBKDF2_SHA256_IDENT: Ident = Ident::new_unwrap(Self::PBKDF2_SHA256_ID);
45+
46+
/// PBKDF2 (SHA-512) algorithm identifier
47+
#[cfg(feature = "phc")]
48+
pub(crate) const PBKDF2_SHA512_IDENT: Ident = Ident::new_unwrap(Self::PBKDF2_SHA512_ID);
3449

3550
/// Default algorithm suggested by the [OWASP cheat sheet]:
3651
///
@@ -45,25 +60,20 @@ impl Algorithm {
4560
id.as_ref().parse()
4661
}
4762

48-
/// Get the [`Ident`] that corresponds to this PBKDF2 [`Algorithm`].
49-
pub fn ident(&self) -> &'static Ident {
63+
/// Get the Modular Crypt Format algorithm identifier for this algorithm.
64+
pub const fn to_str(self) -> &'static str {
5065
match self {
5166
#[cfg(feature = "sha1")]
52-
Algorithm::Pbkdf2Sha1 => &Self::PBKDF2_SHA1_IDENT,
53-
Algorithm::Pbkdf2Sha256 => &Self::PBKDF2_SHA256_IDENT,
54-
Algorithm::Pbkdf2Sha512 => &Self::PBKDF2_SHA512_IDENT,
67+
Algorithm::Pbkdf2Sha1 => Self::PBKDF2_SHA1_ID,
68+
Algorithm::Pbkdf2Sha256 => Self::PBKDF2_SHA256_ID,
69+
Algorithm::Pbkdf2Sha512 => Self::PBKDF2_SHA512_ID,
5570
}
5671
}
57-
58-
/// Get the identifier string for this PBKDF2 [`Algorithm`].
59-
pub fn as_str(&self) -> &str {
60-
self.ident().as_str()
61-
}
6272
}
6373

6474
impl AsRef<str> for Algorithm {
6575
fn as_ref(&self) -> &str {
66-
self.as_str()
76+
self.to_str()
6777
}
6878
}
6979

@@ -75,7 +85,7 @@ impl Default for Algorithm {
7585

7686
impl Display for Algorithm {
7787
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78-
f.write_str(self.as_str())
88+
f.write_str(self.to_str())
7989
}
8090
}
8191

@@ -87,21 +97,27 @@ impl FromStr for Algorithm {
8797
}
8898
}
8999

100+
#[cfg(feature = "phc")]
90101
impl From<Algorithm> for Ident {
91102
fn from(alg: Algorithm) -> Ident {
92-
*alg.ident()
103+
match alg {
104+
#[cfg(feature = "sha1")]
105+
Algorithm::Pbkdf2Sha1 => Algorithm::PBKDF2_SHA1_IDENT,
106+
Algorithm::Pbkdf2Sha256 => Algorithm::PBKDF2_SHA256_IDENT,
107+
Algorithm::Pbkdf2Sha512 => Algorithm::PBKDF2_SHA512_IDENT,
108+
}
93109
}
94110
}
95111

96112
impl<'a> TryFrom<&'a str> for Algorithm {
97113
type Error = Error;
98114

99115
fn try_from(name: &'a str) -> password_hash::Result<Algorithm> {
100-
match name.try_into() {
116+
match name {
101117
#[cfg(feature = "sha1")]
102-
Ok(Self::PBKDF2_SHA1_IDENT) => Ok(Algorithm::Pbkdf2Sha1),
103-
Ok(Self::PBKDF2_SHA256_IDENT) => Ok(Algorithm::Pbkdf2Sha256),
104-
Ok(Self::PBKDF2_SHA512_IDENT) => Ok(Algorithm::Pbkdf2Sha512),
118+
Self::PBKDF2_SHA1_ID => Ok(Algorithm::Pbkdf2Sha1),
119+
Self::PBKDF2_SHA256_ID => Ok(Algorithm::Pbkdf2Sha256),
120+
Self::PBKDF2_SHA512_ID => Ok(Algorithm::Pbkdf2Sha512),
105121
_ => Err(Error::Algorithm),
106122
}
107123
}

pbkdf2/src/lib.rs

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
//!
2222
//! [KDF]: https://github.com/RustCrypto/KDFs
2323
//!
24+
//! ## Low-level API
25+
//!
26+
//! This API operates directly on byte slices:
27+
//!
2428
//! ```
2529
//! # #[cfg(feature = "hmac")] {
2630
//! use hex_literal::hex;
@@ -57,49 +61,54 @@
5761
//! rand_core = { version = "0.6", features = ["std"] }
5862
//! ```
5963
//!
64+
//! ## PHC string API
65+
//!
66+
//! This crate can produce and verify password hash strings encoded in the Password Hashing
67+
//! Competition (PHC) string format.
68+
//!
6069
//! The following example demonstrates the high-level password hashing API:
6170
//!
62-
#![cfg_attr(feature = "password-hash", doc = "```")]
63-
#![cfg_attr(not(feature = "password-hash"), doc = "```ignore")]
71+
#![cfg_attr(all(feature = "getrandom", feature = "phc"), doc = "```")]
72+
#![cfg_attr(not(all(feature = "getrandom", feature = "phc")), doc = "```ignore")]
6473
//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
6574
//! // NOTE: example requires `getrandom` feature is enabled
6675
//!
6776
//! use pbkdf2::{
68-
//! password_hash::{PasswordHasher, PasswordVerifier, phc::PasswordHash},
77+
//! password_hash::{PasswordHasher, PasswordVerifier},
78+
//! phc::PasswordHash,
6979
//! Pbkdf2
7080
//! };
7181
//!
7282
//! let pbkdf2 = Pbkdf2::default(); // Uses `Algorithm::default()` and `Params::RECOMMENDED`
7383
//! let password = b"hunter2"; // Bad password; don't actually use!
7484
//!
7585
//! // Hash password to PHC string ($pbkdf2-sha256$...)
76-
//! let password_hash = pbkdf2.hash_password(password)?.to_string();
86+
//! let pwhash: PasswordHash = pbkdf2.hash_password(password)?;
87+
//! let pwhash_string = pwhash.to_string();
7788
//!
7889
//! // Verify password against PHC string
79-
//! let parsed_hash = PasswordHash::new(&password_hash)?;
80-
//! assert!(pbkdf2.verify_password(password, &parsed_hash).is_ok());
90+
//! let parsed_hash = PasswordHash::new(&pwhash_string)?;
91+
//! pbkdf2.verify_password(password, &parsed_hash)?;
8192
//! # Ok(())
8293
//! # }
8394
//! ```
8495
85-
#[cfg(feature = "password-hash")]
86-
extern crate alloc;
87-
88-
#[cfg(feature = "password-hash")]
89-
pub use password_hash;
96+
#[cfg(feature = "mcf")]
97+
pub mod mcf;
98+
#[cfg(feature = "phc")]
99+
pub mod phc;
90100

91-
#[cfg(feature = "password-hash")]
101+
#[cfg(any(feature = "mcf", feature = "phc"))]
92102
mod algorithm;
93-
#[cfg(feature = "password-hash")]
103+
#[cfg(any(feature = "mcf", feature = "phc"))]
94104
mod params;
95-
#[cfg(feature = "password-hash")]
96-
mod phc;
97105

106+
#[cfg(any(feature = "mcf", feature = "phc"))]
107+
pub use crate::{algorithm::Algorithm, params::Params};
98108
#[cfg(feature = "hmac")]
99109
pub use hmac;
100-
101-
#[cfg(feature = "password-hash")]
102-
pub use crate::{algorithm::Algorithm, params::Params, phc::Pbkdf2};
110+
#[cfg(any(feature = "mcf", feature = "phc"))]
111+
pub use password_hash;
103112

104113
use digest::{FixedOutput, InvalidLength, KeyInit, Update, typenum::Unsigned};
105114

@@ -242,3 +251,42 @@ where
242251
pbkdf2_hmac::<D>(password, salt, rounds, &mut buf);
243252
buf
244253
}
254+
255+
/// PBKDF2 type for use with [`PasswordHasher`].
256+
#[cfg(any(feature = "mcf", feature = "phc"))]
257+
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
258+
pub struct Pbkdf2 {
259+
/// Algorithm to use
260+
algorithm: Algorithm,
261+
262+
/// Default parameters to use.
263+
params: Params,
264+
}
265+
266+
#[cfg(any(feature = "mcf", feature = "phc"))]
267+
impl Pbkdf2 {
268+
/// Initialize [`Pbkdf2`] with default parameters.
269+
pub const fn new(algorithm: Algorithm, params: Params) -> Self {
270+
Self { algorithm, params }
271+
}
272+
}
273+
274+
#[cfg(any(feature = "mcf", feature = "phc"))]
275+
impl From<Algorithm> for Pbkdf2 {
276+
fn from(algorithm: Algorithm) -> Self {
277+
Self {
278+
algorithm,
279+
params: Params::default(),
280+
}
281+
}
282+
}
283+
284+
#[cfg(any(feature = "mcf", feature = "phc"))]
285+
impl From<Params> for Pbkdf2 {
286+
fn from(params: Params) -> Self {
287+
Self {
288+
algorithm: Algorithm::default(),
289+
params,
290+
}
291+
}
292+
}

0 commit comments

Comments
 (0)