Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions pbkdf2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,16 @@ pub struct Pbkdf2 {
#[cfg(feature = "sha2")]
impl Pbkdf2 {
/// PBKDF2 configured with SHA-256 as the default.
pub const SHA256: Self = Self::new(Algorithm::Pbkdf2Sha256, Params::RECOMMENDED);
pub const SHA256: Self = Self::new(
Algorithm::Pbkdf2Sha256,
Params::recommended_for(Algorithm::Pbkdf2Sha256),
);

/// PBKDF2 configured with SHA-512 as the default.
pub const SHA512: Self = Self::new(Algorithm::Pbkdf2Sha512, Params::RECOMMENDED);
pub const SHA512: Self = Self::new(
Algorithm::Pbkdf2Sha512,
Params::recommended_for(Algorithm::Pbkdf2Sha512),
);
}

#[cfg(feature = "sha2")]
Expand All @@ -321,7 +327,7 @@ impl From<Algorithm> for Pbkdf2 {
fn from(algorithm: Algorithm) -> Self {
Self {
algorithm,
params: Params::default(),
params: Params::recommended_for(algorithm),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pbkdf2/src/mcf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl PasswordVerifier<PasswordHashRef> for Pbkdf2 {
let algorithm = hash.id().parse::<Algorithm>()?;
let mut fields = hash.fields();
let mut next = fields.next().ok_or(Error::EncodingInvalid)?;
let mut params = Params::default();
let mut params = Params::recommended_for(algorithm);

// decode params
if let Ok(p) = next.as_str().parse::<Params>() {
Expand Down
30 changes: 28 additions & 2 deletions pbkdf2/src/params.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use core::fmt::{self, Display};

use crate::Algorithm;

#[cfg(feature = "phc")]
use password_hash::phc::{self, Decimal, ParamsString};
#[cfg(feature = "password-hash")]
Expand Down Expand Up @@ -32,15 +34,26 @@ impl Params {
/// Recommended output length.
pub const RECOMMENDED_OUTPUT_LENGTH: usize = 32;

/// Recommended number of PBKDF2 rounds (used by default).
/// Recommended number of PBKDF2-SHA256 rounds (used by default).
///
/// This number is adopted from the [OWASP cheat sheet]:
///
/// > Use PBKDF2 with a work factor of 600,000 or more
/// > Use PBKDF2 with a work factor of 600,000 or more and set with an
/// > internal hash function of HMAC-SHA-256.
///
/// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
pub const RECOMMENDED_ROUNDS: u32 = 600_000;

/// Recommended number of PBKDF2-SHA512 rounds.
///
/// This number is adopted from the [OWASP cheat sheet]:
///
/// > Use PBKDF2 with a work factor of 210,000 or more and set with an
/// > internal hash function of HMAC-SHA-512.
///
/// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
pub const RECOMMENDED_SHA512_ROUNDS: u32 = 210_000;

/// Recommended PBKDF2 parameters adapted from the [OWASP cheat sheet].
///
/// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
Expand All @@ -49,6 +62,19 @@ impl Params {
output_len: Self::RECOMMENDED_OUTPUT_LENGTH,
};

/// Recommended PBKDF2 parameters for the selected algorithm.
pub const fn recommended_for(algorithm: Algorithm) -> Self {
let rounds = match algorithm {
Algorithm::Pbkdf2Sha256 => Self::RECOMMENDED_ROUNDS,
Algorithm::Pbkdf2Sha512 => Self::RECOMMENDED_SHA512_ROUNDS,
};

Self {
rounds,
output_len: Self::RECOMMENDED_OUTPUT_LENGTH,
}
}

/// Create new params with the given number of rounds.
#[cfg(feature = "password-hash")]
pub const fn new(rounds: u32) -> Result<Self> {
Expand Down
50 changes: 50 additions & 0 deletions pbkdf2/tests/pbkdf2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

use belt_hash::BeltHash;
use hex_literal::hex;
#[cfg(all(feature = "sha2", feature = "phc"))]
use pbkdf2::{Params, Pbkdf2, password_hash::PasswordHasher, phc::PasswordHash};
use sha1::Sha1;
use sha2::{Sha256, Sha512};
use streebog::Streebog512;

macro_rules! test {
Expand Down Expand Up @@ -92,3 +95,50 @@ fn pbkdf2_belt() {
"3D331BBB B1FBBB40 E4BF22F6 CB9A689E F13A77DC 09ECF932 91BFE424 39A72E7D";
);
}
#[test]
fn pbkdf2_algorithm_defaults_use_matching_rounds_sha_256() {
test!(
Sha256;
b"password", b"salt", 1, "120fb6cffcf8b32c43e7225256c4f837a86548c9";
b"password", b"salt", 2, "ae4d0c95af6b46d32d0adff928f06dd02a303f8e";
b"password", b"salt", 4096, "c5e478d59288c841aa530db6845c4c8d962893a0";
// this test passes, but takes a long time to execute
// b"password", b"salt", 16777216, "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984";
b"passwordPASSWORDpassword", b"saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096,
"348c89dbcbd32b2f32d814b8116e84cf2b17347e";
b"pass\0word", b"sa\0lt", 600_000, "efc5286bfbd0681c9600b5c024b8ba1b5ae0f0ab";
);
}

#[test]
fn pbkdf2_algorithm_defaults_use_matching_rounds_sha_512() {
test!(
Sha512;
b"password", b"salt", 1, "867f70cf1ade02cff3752599a3a53dc4af34c7a6";
b"password", b"salt", 2, "e1d9c16aa681708a45f5c7c4e215ceb66e011a2e";
b"password", b"salt", 4096, "d197b1b33db0143e018b12f3d1d1479e6cdebdcc";
// this test passes, but takes a long time to execute
// b"password", b"salt", 16777216, "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984";
b"passwordPASSWORDpassword", b"saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096,
"8c0511f4c6e597c6ac6315d8f0362e225f3c5014";
b"pass\0word", b"sa\0lt", 210_000, "4941abc239d618e79f63d3d300e5f81954164bc1";
);
}

#[test]
#[cfg(all(feature = "sha2", feature = "phc"))]
fn pbkdf2_sha_512_default_iterations() {
let hash: PasswordHash = Pbkdf2::SHA512
.hash_password_with_salt(b"pass\0word", b"testsalt")
.unwrap();
assert_eq!(Params::try_from(&hash).unwrap().rounds(), 210_000);
}

#[test]
#[cfg(all(feature = "sha2", feature = "phc"))]
fn pbkdf2_sha_256_default_iterations() {
let hash: PasswordHash = Pbkdf2::SHA256
.hash_password_with_salt(b"pass\0word", b"testsalt")
.unwrap();
assert_eq!(Params::try_from(&hash).unwrap().rounds(), 600_000);
}
Loading