Add impls for kdf::{Kdf, Pbkdf} (#823)

For the password hash algorithms that already have a `struct` where we
can impl traits (i.e. any with a `password-hash`/`phc`/`mcf` feature)
adds feature-gated impls of the traits from the new `kdf` crate.

The `Kdf` trait provides a generic API, and `Pbkdf` is a marker trait
for password-based KDFs where a password can be used as a secret input.
This commit is contained in:
Tony Arcieri
2026-01-20 16:12:13 -07:00
committed by GitHub
parent cd61372f99
commit 2cfdb6f494
28 changed files with 359 additions and 168 deletions

View File

@@ -36,7 +36,7 @@ jobs:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.target }}
- run: cargo build --target ${{ matrix.target }} --no-default-features
- run: cargo build --target ${{ matrix.target }} --no-default-features --features password-hash
- run: cargo build --target ${{ matrix.target }} --no-default-features --features kdf
- run: cargo build --target ${{ matrix.target }} --no-default-features --features password-hash
- run: cargo build --target ${{ matrix.target }} --no-default-features --features zeroize
@@ -73,6 +73,7 @@ jobs:
targets: ${{ matrix.target }}
- run: ${{ matrix.deps }}
- run: cargo test --no-default-features
- run: cargo test --no-default-features --features kdf
- run: cargo test --no-default-features --features password-hash
- run: cargo test
- run: cargo test --all-features

View File

@@ -35,6 +35,7 @@ jobs:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.target }}
- run: cargo build --target ${{ matrix.target }} --release --no-default-features
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features kdf
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features password-hash
minimal-versions:
@@ -56,7 +57,9 @@ jobs:
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- run: cargo test --release
- run: cargo test --release --no-default-features --features alloc
- run: cargo test --release --no-default-features --features alloc,zeroize
- run: cargo test --release --all-features
- run: cargo test
- run: cargo test --no-default-features --features alloc
- run: cargo test --no-default-features --features alloc,zeroize
- run: cargo test --no-default-features --features kdf
- run: cargo test --all-features
- run: cargo test --all-features --release

View File

@@ -56,5 +56,11 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
- uses: RustCrypto/actions/cargo-hack-install@master
- run: cargo hack test --feature-powerset
- run: cargo hack check --feature-powerset --no-dev-deps
- run: cargo hack test --feature-powerset --exclude-features default,getrandom,hmac,kdf,password-hash,rand_core
- run: cargo test --no-default-features --features getrandom
- run: cargo test --no-default-features --features hmac
- run: cargo test --no-default-features --features kdf
- run: cargo test --no-default-features --features password-hash
- run: cargo test --no-default-features --features rand_core
- run: cargo test --all-features --release

View File

@@ -35,6 +35,7 @@ jobs:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.target }}
- run: cargo build --target ${{ matrix.target }} --no-default-features
- run: cargo build --target ${{ matrix.target }} --no-default-features --features kdf
- run: cargo build --target ${{ matrix.target }} --no-default-features --features password-hash
minimal-versions:
@@ -42,7 +43,7 @@ jobs:
if: false
uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
with:
working-directory: ${{ github.workflow }}
working-directory: ${{ github.workflow }}
test:
runs-on: ubuntu-latest
@@ -59,5 +60,8 @@ jobs:
toolchain: ${{ matrix.rust }}
- run: cargo test --no-default-features
- run: cargo test
- run: cargo test --all-features
- run: cargo doc --no-default-features
- run: cargo test --no-default-features --features kdf
- run: cargo test --no-default-features --features mcf
- run: cargo test --no-default-features --features phc
- run: cargo test --all-features --release
- run: cargo test --all-features --release

View File

@@ -24,6 +24,27 @@ jobs:
with:
working-directory: ${{ github.workflow }}
build:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- 1.85.0 # MSRV
- stable
target:
- thumbv7em-none-eabi
- wasm32-unknown-unknown
steps:
- uses: actions/checkout@v6
- uses: RustCrypto/actions/cargo-cache@master
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.target }}
- run: cargo build --target ${{ matrix.target }} --no-default-features
- run: cargo build --target ${{ matrix.target }} --no-default-features --features kdf
- run: cargo build --target ${{ matrix.target }} --no-default-features --features password-hash
test:
runs-on: ubuntu-latest
strategy:

11
Cargo.lock generated
View File

@@ -10,6 +10,7 @@ dependencies = [
"blake2",
"cpufeatures",
"hex-literal",
"kdf",
"password-hash",
"rayon",
"zeroize",
@@ -28,6 +29,7 @@ dependencies = [
"crypto-bigint",
"digest",
"hex-literal",
"kdf",
"password-hash",
"rayon",
"sha2",
@@ -274,6 +276,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kdf"
version = "0.1.0-pre.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4852c654c9650d06a4293146ead04bedcf29861dc0de1115ca1492b7a27c50aa"
[[package]]
name = "libc"
version = "0.2.179"
@@ -334,6 +342,7 @@ dependencies = [
"digest",
"hex-literal",
"hmac",
"kdf",
"mcf",
"password-hash",
"sha1",
@@ -434,6 +443,7 @@ name = "scrypt"
version = "0.12.0-rc.8"
dependencies = [
"cfg-if",
"kdf",
"mcf",
"password-hash",
"pbkdf2",
@@ -580,6 +590,7 @@ version = "0.1.0-rc.3"
dependencies = [
"hex-literal",
"hmac",
"kdf",
"mcf",
"password-hash",
"pbkdf2",

View File

@@ -21,6 +21,7 @@ base64ct = "1.7"
blake2 = { version = "0.11.0-rc.3", default-features = false }
# optional dependencies
kdf = { version = "0.1.0-pre.1", optional = true }
rayon = { version = "1.7", optional = true }
password-hash = { version = "0.6.0-rc.9", optional = true, features = ["phc"] }
zeroize = { version = "1", default-features = false, optional = true }
@@ -35,6 +36,7 @@ hex-literal = "1"
default = ["alloc", "getrandom", "password-hash"]
alloc = ["password-hash?/alloc"]
kdf = ["alloc", "dep:kdf"]
getrandom = ["password-hash/getrandom"]
parallel = ["dep:rayon"]
password-hash = ["dep:password-hash"]

View File

@@ -60,6 +60,15 @@ pub enum Error {
OutOfMemory,
}
impl core::error::Error for Error {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::B64Encoding(err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
@@ -90,6 +99,13 @@ impl From<base64ct::Error> for Error {
}
}
#[cfg(feature = "kdf")]
impl From<Error> for kdf::Error {
fn from(_err: Error) -> kdf::Error {
kdf::Error
}
}
#[cfg(feature = "password-hash")]
impl From<Error> for password_hash::Error {
fn from(err: Error) -> password_hash::Error {
@@ -114,12 +130,3 @@ impl From<Error> for password_hash::Error {
}
}
}
impl core::error::Error for Error {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::B64Encoding(err) => Some(err),
_ => None,
}
}
}

View File

@@ -148,6 +148,8 @@ pub use crate::{
version::Version,
};
#[cfg(feature = "kdf")]
pub use kdf::{self, Kdf, Pbkdf};
#[cfg(feature = "password-hash")]
pub use {
crate::algorithm::{ARGON2D_IDENT, ARGON2I_IDENT, ARGON2ID_IDENT},
@@ -608,6 +610,17 @@ impl<'key> Argon2<'key> {
}
}
#[cfg(feature = "kdf")]
impl Kdf for Argon2<'_> {
fn derive_key(&self, password: &[u8], salt: &[u8], out: &mut [u8]) -> kdf::Result<()> {
self.hash_password_into(password, &salt, out)?;
Ok(())
}
}
#[cfg(feature = "kdf")]
impl Pbkdf for Argon2<'_> {}
#[cfg(all(feature = "alloc", feature = "password-hash"))]
impl CustomizedPasswordHasher<PasswordHash> for Argon2<'_> {
type Params = Params;

View File

@@ -18,6 +18,7 @@ digest = { version = "0.11.0-rc.4", default-features = false }
crypto-bigint = { version = "0.7.0-rc.9", default-features = false, features = ["hybrid-array"] }
# optional dependencies
kdf = { version = "0.1.0-pre.1", optional = true }
password-hash = { version = "0.6.0-rc.9", optional = true, default-features = false, features = ["phc"] }
rayon = { version = "1.7", optional = true }
zeroize = { version = "1", default-features = false, optional = true }
@@ -30,6 +31,7 @@ sha2 = "0.11.0-rc.3"
default = ["alloc", "getrandom", "password-hash"]
alloc = ["password-hash/alloc"]
kdf = ["alloc", "dep:kdf"]
getrandom = ["password-hash/getrandom"]
parallel = ["dep:rayon"]
password-hash = ["dep:password-hash"]

View File

@@ -28,6 +28,8 @@ pub enum Error {
},
}
impl core::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -43,6 +45,13 @@ impl fmt::Display for Error {
}
}
#[cfg(feature = "kdf")]
impl From<Error> for kdf::Error {
fn from(_err: Error) -> kdf::Error {
kdf::Error
}
}
#[cfg(feature = "password-hash")]
impl From<Error> for password_hash::Error {
fn from(err: Error) -> password_hash::Error {
@@ -56,5 +65,3 @@ impl From<Error> for password_hash::Error {
}
}
}
impl core::error::Error for Error {}

View File

@@ -65,6 +65,8 @@ use digest::array::Array;
use digest::typenum::Unsigned;
use digest::{Digest, FixedOutputReset};
#[cfg(feature = "kdf")]
pub use kdf::{self, Kdf, Pbkdf};
#[cfg(all(feature = "alloc", feature = "password-hash"))]
pub use password_hash::phc::Salt;
@@ -95,8 +97,9 @@ where
pub secret: Option<&'key [u8]>,
}
impl<'key, D: Digest + FixedOutputReset> Balloon<'key, D>
impl<'key, D> Balloon<'key, D>
where
D: Digest + FixedOutputReset,
Array<u8, D::OutputSize>: ArrayDecoding,
{
/// Create a new Balloon context.
@@ -111,10 +114,13 @@ where
/// Hash a password and associated parameters.
#[cfg(feature = "alloc")]
pub fn hash(&self, pwd: &[u8], salt: &[u8]) -> Result<Array<u8, D::OutputSize>> {
pub fn hash_password_to_array(
&self,
pwd: &[u8],
salt: &[u8],
) -> Result<Array<u8, D::OutputSize>> {
let mut output = Array::default();
self.hash_into(pwd, salt, &mut output)?;
self.hash_password_into(pwd, salt, &mut output)?;
Ok(output)
}
@@ -122,13 +128,13 @@ where
///
/// The `output` has to have the same size as the hash output size: `D::OutputSize`.
#[cfg(feature = "alloc")]
pub fn hash_into(&self, pwd: &[u8], salt: &[u8], output: &mut [u8]) -> Result<()> {
pub fn hash_password_into(&self, pwd: &[u8], salt: &[u8], output: &mut [u8]) -> Result<()> {
#[cfg(not(feature = "parallel"))]
let mut memory = alloc::vec![Array::default(); self.params.s_cost.get() as usize];
#[cfg(feature = "parallel")]
let mut memory = alloc::vec![Array::default(); (self.params.s_cost.get() * self.params.p_cost.get()) as usize];
self.hash_into_with_memory(pwd, salt, &mut memory, output)?;
self.hash_password_into_with_memory(pwd, salt, &mut memory, output)?;
#[cfg(feature = "zeroize")]
memory.iter_mut().for_each(|block| block.zeroize());
Ok(())
@@ -139,19 +145,19 @@ where
/// This method takes an explicit `memory_blocks` parameter which allows
/// the caller to provide the backing storage for the algorithm's state:
///
/// - Users with the `alloc` feature enabled can use [`Balloon::hash`]
/// - Users with the `alloc` feature enabled can use [`Balloon::hash_password`]
/// to have it allocated for them.
/// - `no_std` users on "heapless" targets can use an array of the [`Array`] type
/// to stack allocate this buffer. It needs a minimum size of `s_cost` or `s_cost * p_cost`
/// with the `parallel` crate feature enabled.
pub fn hash_with_memory(
pub fn hash_password_with_memory(
&self,
pwd: &[u8],
salt: &[u8],
memory_blocks: &mut [Array<u8, D::OutputSize>],
) -> Result<Array<u8, D::OutputSize>> {
let mut output = Array::default();
self.hash_into_with_memory(pwd, salt, memory_blocks, &mut output)?;
self.hash_password_into_with_memory(pwd, salt, memory_blocks, &mut output)?;
Ok(output)
}
@@ -160,8 +166,8 @@ where
///
/// The `output` has to have the same size as the hash output size: `D::OutputSize`.
///
/// See [`Balloon::hash_with_memory`] for more details.
pub fn hash_into_with_memory(
/// See [`Balloon::hash_password_with_memory`] for more details.
pub fn hash_password_into_with_memory(
&self,
pwd: &[u8],
salt: &[u8],
@@ -192,6 +198,26 @@ where
}
}
#[cfg(feature = "kdf")]
impl<D> Kdf for Balloon<'_, D>
where
D: Digest + FixedOutputReset,
Array<u8, D::OutputSize>: ArrayDecoding,
{
fn derive_key(&self, password: &[u8], salt: &[u8], out: &mut [u8]) -> kdf::Result<()> {
self.hash_password_into(password, &salt, out)?;
Ok(())
}
}
#[cfg(feature = "kdf")]
impl<D> Pbkdf for Balloon<'_, D>
where
D: Digest + FixedOutputReset,
Array<u8, D::OutputSize>: ArrayDecoding,
{
}
#[cfg(all(feature = "alloc", feature = "password-hash"))]
impl<D> CustomizedPasswordHasher<PasswordHash> for Balloon<'_, D>
where
@@ -235,7 +261,7 @@ where
salt: &[u8],
) -> password_hash::Result<PasswordHash> {
let salt = Salt::new(salt)?;
let hash = self.hash(password, &salt)?;
let hash = self.hash_password_to_array(password, &salt)?;
let output = Output::new(&hash)?;
Ok(PasswordHash {

View File

@@ -64,7 +64,7 @@ fn test_vectors() {
assert_eq!(
balloon
.hash_with_memory(test_vector.password, test_vector.salt, &mut memory)
.hash_password_with_memory(test_vector.password, test_vector.salt, &mut memory)
.unwrap()
.as_slice(),
test_vector.output,

View File

@@ -100,7 +100,7 @@ fn test_vectors() {
assert_eq!(
balloon
.hash_with_memory(test_vector.password, test_vector.salt, &mut memory)
.hash_password_with_memory(test_vector.password, test_vector.salt, &mut memory)
.unwrap()
.as_slice(),
test_vector.output,

View File

@@ -18,6 +18,7 @@ digest = { version = "0.11.0-rc.4", features = ["mac"] }
# optional dependencies
hmac = { version = "0.13.0-rc.3", optional = true, default-features = false }
kdf = { version = "0.1.0-pre.1", optional = true }
mcf = { version = "0.6.0-rc.3", optional = true, default-features = false, features = ["base64"] }
password-hash = { version = "0.6.0-rc.9", default-features = false, optional = true }
sha1 = { version = "0.11.0-rc.3", default-features = false, optional = true }
@@ -34,10 +35,14 @@ belt-hash = "0.2.0-rc.3"
[features]
default = ["hmac"]
alloc = ["mcf?/alloc", "password-hash?/alloc"]
kdf = ["sha2", "dep:kdf"]
getrandom = ["password-hash/getrandom"]
mcf = ["hmac", "sha2", "dep:password-hash", "dep:mcf"]
phc = ["hmac", "password-hash/phc", "sha2"]
mcf = ["sha2", "password-hash", "dep:mcf"]
phc = ["password-hash/phc", "sha2"]
rand_core = ["password-hash/rand_core"]
sha1 = ["hmac", "dep:sha1"]
sha2 = ["hmac", "dep:sha2"]
[package.metadata.docs.rs]
all-features = true

View File

@@ -1,11 +1,9 @@
use core::{
fmt::{self, Display},
str::FromStr,
};
use password_hash::Error;
use core::fmt::{self, Display};
#[cfg(feature = "phc")]
use password_hash::phc::Ident;
#[cfg(feature = "password-hash")]
use {core::str::FromStr, password_hash::Error};
/// PBKDF2 variants.
///
@@ -13,14 +11,16 @@ use password_hash::phc::Ident;
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Algorithm {
/// PBKDF2 SHA1
/// PBKDF2-HMAC-SHA1 a.k.a. `$pbkdf2`
#[cfg(feature = "sha1")]
Pbkdf2Sha1,
/// PBKDF2 SHA-256
/// PBKDF2-HMAC-SHA-256 a.k.a. `$pbkdf2-sha256`
#[cfg(feature = "sha2")]
Pbkdf2Sha256,
/// PBKDF2 SHA-512
/// PBKDF2-HMAC-SHA-512 a.k.a. `$pbkdf2-sha512`
#[cfg(feature = "sha2")]
Pbkdf2Sha512,
}
@@ -30,9 +30,11 @@ impl Algorithm {
pub const PBKDF2_SHA1_ID: &'static str = "pbkdf2";
/// PBKDF2 (SHA-256) algorithm identifier
#[cfg(feature = "sha2")]
pub const PBKDF2_SHA256_ID: &'static str = "pbkdf2-sha256";
/// PBKDF2 (SHA-512) algorithm identifier
#[cfg(feature = "sha2")]
pub const PBKDF2_SHA512_ID: &'static str = "pbkdf2-sha512";
/// PBKDF2 (SHA-1) algorithm identifier
@@ -53,9 +55,11 @@ impl Algorithm {
/// > internal hash function of HMAC-SHA-256.
///
/// [OWASP cheat sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
#[cfg(feature = "sha2")]
pub const RECOMMENDED: Self = Self::Pbkdf2Sha256;
/// Parse an [`Algorithm`] from the provided string.
#[cfg(feature = "password-hash")]
pub fn new(id: impl AsRef<str>) -> password_hash::Result<Self> {
id.as_ref().parse()
}
@@ -65,7 +69,9 @@ impl Algorithm {
match self {
#[cfg(feature = "sha1")]
Algorithm::Pbkdf2Sha1 => Self::PBKDF2_SHA1_ID,
#[cfg(feature = "sha2")]
Algorithm::Pbkdf2Sha256 => Self::PBKDF2_SHA256_ID,
#[cfg(feature = "sha2")]
Algorithm::Pbkdf2Sha512 => Self::PBKDF2_SHA512_ID,
}
}
@@ -77,6 +83,7 @@ impl AsRef<str> for Algorithm {
}
}
#[cfg(feature = "sha2")]
impl Default for Algorithm {
fn default() -> Self {
Self::RECOMMENDED
@@ -89,6 +96,7 @@ impl Display for Algorithm {
}
}
#[cfg(feature = "password-hash")]
impl FromStr for Algorithm {
type Err = Error;
@@ -109,6 +117,7 @@ impl From<Algorithm> for Ident {
}
}
#[cfg(feature = "password-hash")]
impl<'a> TryFrom<&'a str> for Algorithm {
type Error = Error;
@@ -116,7 +125,9 @@ impl<'a> TryFrom<&'a str> for Algorithm {
match name {
#[cfg(feature = "sha1")]
Self::PBKDF2_SHA1_ID => Ok(Algorithm::Pbkdf2Sha1),
#[cfg(feature = "sha2")]
Self::PBKDF2_SHA256_ID => Ok(Algorithm::Pbkdf2Sha256),
#[cfg(feature = "sha2")]
Self::PBKDF2_SHA512_ID => Ok(Algorithm::Pbkdf2Sha512),
_ => Err(Error::Algorithm),
}

View File

@@ -25,11 +25,12 @@
//!
//! This API operates directly on byte slices:
//!
//! ```
//! # #[cfg(feature = "hmac")] {
#![cfg_attr(feature = "sha2", doc = "```")]
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
//! // NOTE: example requires `getrandom` feature is enabled
//!
//! use hex_literal::hex;
//! use pbkdf2::{pbkdf2_hmac, pbkdf2_hmac_array};
//! use sha2::Sha256;
//! use pbkdf2::{pbkdf2_hmac, pbkdf2_hmac_array, sha2::Sha256};
//!
//! let password = b"password";
//! let salt = b"salt";
@@ -44,27 +45,14 @@
//!
//! let key2 = pbkdf2_hmac_array::<Sha256, 20>(password, salt, n);
//! assert_eq!(key2, expected);
//! # }
//! ```
//!
//! If you want to use a different PRF, then you can use [`pbkdf2`] and [`pbkdf2_array`] functions.
//!
//! This crates also provides the high-level password-hashing API through
//! the [`Pbkdf2`] struct and traits defined in the
//! [`password-hash`][password_hash] crate.
//!
//! Add the following to your crate's `Cargo.toml` to import it:
//!
//! ```toml
//! [dependencies]
//! pbkdf2 = { version = "0.12", features = ["password-hash"] }
//! rand_core = { version = "0.6", features = ["std"] }
//! ```
//!
//! ## PHC string API
//!
//! This crate can produce and verify password hash strings encoded in the Password Hashing
//! Competition (PHC) string format.
//! Competition (PHC) string format using the [`Pbkdf2`] struct.
//!
//! The following example demonstrates the high-level password hashing API:
//!
@@ -98,12 +86,12 @@ pub mod mcf;
#[cfg(feature = "phc")]
pub mod phc;
#[cfg(any(feature = "mcf", feature = "phc"))]
#[cfg(any(feature = "sha1", feature = "sha2"))]
mod algorithm;
#[cfg(any(feature = "mcf", feature = "phc"))]
#[cfg(any(feature = "sha1", feature = "sha2"))]
mod params;
#[cfg(any(feature = "mcf", feature = "phc"))]
#[cfg(any(feature = "sha1", feature = "sha2"))]
pub use crate::{algorithm::Algorithm, params::Params};
#[cfg(feature = "hmac")]
pub use hmac;
@@ -111,11 +99,17 @@ pub use hmac;
pub use password_hash;
#[cfg(any(feature = "mcf", feature = "phc"))]
pub use password_hash::{PasswordHasher, PasswordVerifier};
#[cfg(feature = "sha1")]
pub use sha1;
#[cfg(feature = "sha2")]
pub use sha2;
use digest::{FixedOutput, InvalidLength, KeyInit, Update, typenum::Unsigned};
#[cfg(feature = "hmac")]
use hmac::EagerHash;
#[cfg(feature = "kdf")]
use kdf::{Kdf, Pbkdf};
#[inline(always)]
fn xor(res: &mut [u8], salt: &[u8]) {
@@ -153,11 +147,10 @@ where
/// Generic implementation of PBKDF2 algorithm which accepts an arbitrary keyed PRF.
///
/// ```
#[cfg_attr(feature = "sha2", doc = "```")]
#[cfg_attr(not(feature = "sha2"), doc = "```ignore")]
/// use hex_literal::hex;
/// use pbkdf2::pbkdf2;
/// use hmac::Hmac;
/// use sha2::Sha256;
/// use pbkdf2::{pbkdf2, hmac::Hmac, sha2::Sha256};
///
/// let mut buf = [0u8; 20];
/// pbkdf2::<Hmac<Sha256>>(b"password", b"salt", 600_000, &mut buf)
@@ -186,11 +179,10 @@ where
/// A variant of the [`pbkdf2`] function which returns an array instead of filling an input slice.
///
/// ```
#[cfg_attr(feature = "sha2", doc = "```")]
#[cfg_attr(not(feature = "sha2"), doc = "```ignore")]
/// use hex_literal::hex;
/// use pbkdf2::pbkdf2_array;
/// use hmac::Hmac;
/// use sha2::Sha256;
/// use pbkdf2::{pbkdf2_array, hmac::Hmac, sha2::Sha256};
///
/// let res = pbkdf2_array::<Hmac<Sha256>, 20>(b"password", b"salt", 600_000)
/// .expect("HMAC can be initialized with any key length");
@@ -213,10 +205,10 @@ where
///
/// It's generic over (eager) hash functions.
///
/// ```
#[cfg_attr(feature = "sha2", doc = "```")]
#[cfg_attr(not(feature = "sha2"), doc = "```ignore")]
/// use hex_literal::hex;
/// use pbkdf2::pbkdf2_hmac;
/// use sha2::Sha256;
/// use pbkdf2::{pbkdf2_hmac, sha2::Sha256};
///
/// let mut buf = [0u8; 20];
/// pbkdf2_hmac::<Sha256>(b"password", b"salt", 600_000, &mut buf);
@@ -227,17 +219,17 @@ pub fn pbkdf2_hmac<D>(password: &[u8], salt: &[u8], rounds: u32, res: &mut [u8])
where
D: EagerHash<Core: Sync>,
{
crate::pbkdf2::<hmac::Hmac<D>>(password, salt, rounds, res)
pbkdf2::<hmac::Hmac<D>>(password, salt, rounds, res)
.expect("HMAC can be initialized with any key length");
}
/// A variant of the [`pbkdf2_hmac`] function which returns an array
/// instead of filling an input slice.
///
/// ```
#[cfg_attr(feature = "sha2", doc = "```")]
#[cfg_attr(not(feature = "sha2"), doc = "```ignore")]
/// use hex_literal::hex;
/// use pbkdf2::pbkdf2_hmac_array;
/// use sha2::Sha256;
/// use pbkdf2::{pbkdf2_hmac_array, sha2::Sha256};
///
/// assert_eq!(
/// pbkdf2_hmac_array::<Sha256, 20>(b"password", b"salt", 600_000),
@@ -254,14 +246,50 @@ where
buf
}
/// API for using [`pbkdf2_hmac`] which supports the [`Algorithm`] and [`Params`] types and with
/// it runtime selection of which algorithm to use.
///
#[cfg_attr(feature = "sha2", doc = "```")]
#[cfg_attr(not(feature = "sha2"), doc = "```ignore")]
/// use hex_literal::hex;
/// use pbkdf2::pbkdf2_hmac_with_params;
///
/// let algorithm = pbkdf2::Algorithm::Pbkdf2Sha256;
/// let params = pbkdf2::Params::default();
///
/// let mut buf = [0u8; 32];
/// pbkdf2_hmac_with_params(b"password", b"salt", algorithm, params, &mut buf);
/// assert_eq!(buf, hex!("669cfe52482116fda1aa2cbe409b2f56c8e4563752b7a28f6eaab614ee005178"));
/// ```
#[cfg(any(feature = "sha1", feature = "sha2"))]
pub fn pbkdf2_hmac_with_params(
password: &[u8],
salt: &[u8],
algorithm: Algorithm,
params: Params,
out: &mut [u8],
) {
let f = match algorithm {
#[cfg(feature = "sha1")]
Algorithm::Pbkdf2Sha1 => pbkdf2_hmac::<sha1::Sha1>,
#[cfg(feature = "sha2")]
Algorithm::Pbkdf2Sha256 => pbkdf2_hmac::<sha2::Sha256>,
#[cfg(feature = "sha2")]
Algorithm::Pbkdf2Sha512 => pbkdf2_hmac::<sha2::Sha512>,
};
f(password, salt, params.rounds(), out);
}
/// PBKDF2 type for use with the [`PasswordHasher`] and [`PasswordVerifier`] traits, which
/// implements support for password hash strings.
///
/// Supports the following password hash string formats, gated under the following crate features:
/// - `mcf`: support for the Modular Crypt Format
/// - `phc`: support for the Password Hashing Competition string format
#[cfg(any(feature = "mcf", feature = "phc"))]
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[cfg(any(feature = "sha1", feature = "sha2"))]
#[cfg_attr(feature = "sha2", derive(Default))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Pbkdf2 {
/// Algorithm to use
algorithm: Algorithm,
@@ -270,7 +298,7 @@ pub struct Pbkdf2 {
params: Params,
}
#[cfg(any(feature = "mcf", feature = "phc"))]
#[cfg(feature = "sha2")]
impl Pbkdf2 {
/// PBKDF2 configured with SHA-256 as the default.
pub const SHA256: Self = Self::new(Algorithm::Pbkdf2Sha256, Params::RECOMMENDED);
@@ -279,15 +307,20 @@ impl Pbkdf2 {
pub const SHA512: Self = Self::new(Algorithm::Pbkdf2Sha512, Params::RECOMMENDED);
}
#[cfg(any(feature = "mcf", feature = "phc"))]
#[cfg(any(feature = "sha1", feature = "sha2"))]
impl Pbkdf2 {
/// Initialize [`Pbkdf2`] with default parameters.
pub const fn new(algorithm: Algorithm, params: Params) -> Self {
Self { algorithm, params }
}
/// Hash password into the given output buffer using the configured params.
pub fn hash_password_into(&self, password: &[u8], salt: &[u8], out: &mut [u8]) {
pbkdf2_hmac_with_params(password, salt, self.algorithm, self.params, out);
}
}
#[cfg(any(feature = "mcf", feature = "phc"))]
#[cfg(any(feature = "sha1", feature = "sha2"))]
impl From<Algorithm> for Pbkdf2 {
fn from(algorithm: Algorithm) -> Self {
Self {
@@ -297,7 +330,7 @@ impl From<Algorithm> for Pbkdf2 {
}
}
#[cfg(any(feature = "mcf", feature = "phc"))]
#[cfg(feature = "sha2")]
impl From<Params> for Pbkdf2 {
fn from(params: Params) -> Self {
Self {
@@ -306,3 +339,14 @@ impl From<Params> for Pbkdf2 {
}
}
}
#[cfg(feature = "kdf")]
impl Kdf for Pbkdf2 {
fn derive_key(&self, password: &[u8], salt: &[u8], out: &mut [u8]) -> kdf::Result<()> {
self.hash_password_into(password, salt, out);
Ok(())
}
}
#[cfg(feature = "kdf")]
impl Pbkdf for Pbkdf2 {}

View File

@@ -12,15 +12,12 @@ pub use mcf::PasswordHashRef;
#[cfg(feature = "alloc")]
pub use mcf::PasswordHash;
use crate::{Algorithm, Params, Pbkdf2, pbkdf2_hmac};
use crate::{Algorithm, Params, Pbkdf2, pbkdf2_hmac_with_params};
use mcf::Base64;
use password_hash::{Error, PasswordVerifier, Result};
use sha2::{Sha256, Sha512};
#[cfg(feature = "alloc")]
use password_hash::{CustomizedPasswordHasher, PasswordHasher, Version};
#[cfg(feature = "sha1")]
use sha1::Sha1;
const MAX_SALT_LEN: usize = 64;
@@ -50,14 +47,7 @@ impl CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {
.get_mut(..params.output_len())
.ok_or(Error::OutputSize)?;
let f = match algorithm {
#[cfg(feature = "sha1")]
Algorithm::Pbkdf2Sha1 => pbkdf2_hmac::<Sha1>,
Algorithm::Pbkdf2Sha256 => pbkdf2_hmac::<Sha256>,
Algorithm::Pbkdf2Sha512 => pbkdf2_hmac::<Sha512>,
};
f(password, salt, params.rounds(), out);
pbkdf2_hmac_with_params(password, salt, algorithm, params, out);
let mut mcf_hash = PasswordHash::from_id(algorithm.to_str()).expect("should have valid ID");
mcf_hash
@@ -118,14 +108,7 @@ impl PasswordVerifier<PasswordHashRef> for Pbkdf2 {
let mut out_buf = [0u8; Params::MAX_OUTPUT_LENGTH];
let out = out_buf.get_mut(..expected.len()).ok_or(Error::OutputSize)?;
let f = match algorithm {
#[cfg(feature = "sha1")]
Algorithm::Pbkdf2Sha1 => pbkdf2_hmac::<Sha1>,
Algorithm::Pbkdf2Sha256 => pbkdf2_hmac::<Sha256>,
Algorithm::Pbkdf2Sha512 => pbkdf2_hmac::<Sha512>,
};
f(password, &salt, params.rounds(), out);
pbkdf2_hmac_with_params(password, salt, algorithm, params, out);
// TODO(tarcieri): use `subtle` or `ctutils` for comparison
if out

View File

@@ -1,11 +1,12 @@
use core::{
fmt::{self, Display},
str::FromStr,
};
use password_hash::{Error, Result};
use core::fmt::{self, Display};
#[cfg(feature = "phc")]
use password_hash::phc::{self, Decimal, ParamsString};
#[cfg(feature = "password-hash")]
use {
core::str::FromStr,
password_hash::{Error, Result},
};
/// PBKDF2 params
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -49,11 +50,13 @@ impl Params {
};
/// Create new params with the given number of rounds.
#[cfg(feature = "password-hash")]
pub const fn new(rounds: u32) -> Result<Self> {
Self::new_with_output_len(rounds, Self::RECOMMENDED_OUTPUT_LENGTH)
}
/// Create new params with a customized output length.
#[cfg(feature = "password-hash")]
pub const fn new_with_output_len(rounds: u32, output_len: usize) -> Result<Self> {
if rounds < Self::MIN_ROUNDS
|| output_len < Self::MIN_OUTPUT_LENGTH
@@ -88,6 +91,7 @@ impl Display for Params {
}
}
#[cfg(feature = "password-hash")]
impl FromStr for Params {
type Err = Error;
@@ -98,6 +102,7 @@ impl FromStr for Params {
}
}
#[cfg(feature = "password-hash")]
impl TryFrom<u32> for Params {
type Error = Error;

View File

@@ -2,15 +2,11 @@
pub use password_hash::phc::PasswordHash;
use crate::{Algorithm, Params, Pbkdf2, pbkdf2_hmac};
use crate::{Algorithm, Params, Pbkdf2, pbkdf2_hmac_with_params};
use password_hash::{
CustomizedPasswordHasher, Error, PasswordHasher, Result,
phc::{Output, Salt},
};
use sha2::{Sha256, Sha512};
#[cfg(feature = "sha1")]
use sha1::Sha1;
impl CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {
type Params = Params;
@@ -40,14 +36,7 @@ impl CustomizedPasswordHasher<PasswordHash> for Pbkdf2 {
.get_mut(..params.output_len())
.ok_or(Error::OutputSize)?;
let f = match algorithm {
#[cfg(feature = "sha1")]
Algorithm::Pbkdf2Sha1 => pbkdf2_hmac::<Sha1>,
Algorithm::Pbkdf2Sha256 => pbkdf2_hmac::<Sha256>,
Algorithm::Pbkdf2Sha512 => pbkdf2_hmac::<Sha512>,
};
f(password, &salt, params.rounds(), out);
pbkdf2_hmac_with_params(password, salt.as_ref(), algorithm, params, out);
let output = Output::new(out)?;
Ok(PasswordHash {
@@ -84,8 +73,8 @@ mod tests {
/// Input:
/// - P = "passwordPASSWORDpassword" (24 octets)
/// - S = "saltSALTsaltSALTsaltSALTsaltSALTsalt" (36 octets)
/// c = 4096
/// dkLen = 40
/// - c = 4096
/// - dkLen = 40
#[test]
fn hash_with_default_algorithm() {
let params = Params::new_with_output_len(4096, 40).unwrap();

View File

@@ -21,6 +21,7 @@ sha2 = { version = "0.11.0-rc.3", default-features = false }
rayon = { version = "1.11", optional = true }
# optional dependencies
kdf = { version = "0.1.0-pre.1", optional = true }
mcf = { version = "0.6.0-rc.2", optional = true }
password-hash = { version = "0.6.0-rc.9", optional = true, default-features = false }
subtle = { version = "2", optional = true, default-features = false }
@@ -29,7 +30,8 @@ subtle = { version = "2", optional = true, default-features = false }
alloc = ["password-hash?/alloc"]
getrandom = ["password-hash", "password-hash/getrandom"]
mcf = ["alloc", "password-hash", "dep:mcf", "dep:subtle"]
kdf = ["alloc", "dep:kdf"]
mcf = ["alloc", "phc", "dep:mcf", "dep:subtle"]
phc = ["password-hash/phc"]
rand_core = ["password-hash/rand_core"]
parallel = ["dep:rayon"]

View File

@@ -16,6 +16,13 @@ impl fmt::Display for InvalidOutputLen {
impl core::error::Error for InvalidOutputLen {}
#[cfg(feature = "kdf")]
impl From<InvalidOutputLen> for kdf::Error {
fn from(_err: InvalidOutputLen) -> kdf::Error {
kdf::Error
}
}
impl fmt::Display for InvalidParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid scrypt parameters")

View File

@@ -62,6 +62,8 @@ pub mod phc;
pub use crate::params::Params;
#[cfg(feature = "kdf")]
pub use kdf::{self, Kdf, Pbkdf};
#[cfg(feature = "password-hash")]
pub use password_hash;
@@ -146,14 +148,14 @@ fn romix_parallel(nr128: usize, r128: usize, n: usize, b: &mut [u8]) {
/// This type holds the default parameters to use when computing password hashes.
///
/// See the toplevel documentation for a code example.
#[cfg(any(feature = "mcf", feature = "phc"))]
#[cfg(any(feature = "kdf", feature = "mcf", feature = "phc"))]
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Scrypt {
/// Default parameters to use.
params: Params,
}
#[cfg(any(feature = "mcf", feature = "phc"))]
#[cfg(any(feature = "kdf", feature = "mcf", feature = "phc"))]
impl Scrypt {
/// Initialize [`Scrypt`] with default parameters.
pub const fn new() -> Self {
@@ -168,9 +170,20 @@ impl Scrypt {
}
}
#[cfg(any(feature = "mcf", feature = "phc"))]
#[cfg(any(feature = "kdf", feature = "mcf", feature = "phc"))]
impl From<Params> for Scrypt {
fn from(params: Params) -> Self {
Self::new_with_params(params)
}
}
#[cfg(feature = "kdf")]
impl Kdf for Scrypt {
fn derive_key(&self, password: &[u8], salt: &[u8], out: &mut [u8]) -> kdf::Result<()> {
scrypt(password, salt, &self.params, out)?;
Ok(())
}
}
#[cfg(feature = "kdf")]
impl Pbkdf for Scrypt {}

View File

@@ -6,10 +6,7 @@ use {
fmt::{self, Display},
str::FromStr,
},
password_hash::{
Error,
phc::{Decimal, Output, ParamsString, PasswordHash},
},
password_hash::{Error, phc},
};
#[cfg(all(feature = "phc", doc))]
@@ -125,7 +122,7 @@ impl Params {
p: u32,
len: usize,
) -> Result<Params, InvalidParams> {
if !(Output::MIN_LENGTH..=Output::MAX_LENGTH).contains(&len) {
if !(phc::Output::MIN_LENGTH..=phc::Output::MAX_LENGTH).contains(&len) {
return Err(InvalidParams);
}
@@ -179,7 +176,9 @@ impl Default for Params {
#[cfg(feature = "phc")]
impl Display for Params {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
ParamsString::try_from(self).map_err(|_| fmt::Error)?.fmt(f)
phc::ParamsString::try_from(self)
.map_err(|_| fmt::Error)?
.fmt(f)
}
}
@@ -188,16 +187,16 @@ impl FromStr for Params {
type Err = Error;
fn from_str(s: &str) -> password_hash::Result<Self> {
let params_string = ParamsString::from_str(s).map_err(|_| Error::ParamsInvalid)?;
let params_string = phc::ParamsString::from_str(s).map_err(|_| Error::ParamsInvalid)?;
Self::try_from(&params_string)
}
}
#[cfg(feature = "phc")]
impl TryFrom<&ParamsString> for Params {
impl TryFrom<&phc::ParamsString> for Params {
type Error = Error;
fn try_from(params: &ParamsString) -> password_hash::Result<Self> {
fn try_from(params: &phc::ParamsString) -> password_hash::Result<Self> {
let mut log_n = Self::RECOMMENDED_LOG_N;
let mut r = Self::RECOMMENDED_R;
let mut p = Self::RECOMMENDED_P;
@@ -230,10 +229,10 @@ impl TryFrom<&ParamsString> for Params {
}
#[cfg(feature = "phc")]
impl TryFrom<&PasswordHash> for Params {
impl TryFrom<&phc::PasswordHash> for Params {
type Error = Error;
fn try_from(hash: &PasswordHash) -> password_hash::Result<Self> {
fn try_from(hash: &phc::PasswordHash) -> password_hash::Result<Self> {
if hash.version.is_some() {
return Err(Error::Version);
}
@@ -251,23 +250,23 @@ impl TryFrom<&PasswordHash> for Params {
}
#[cfg(feature = "phc")]
impl TryFrom<Params> for ParamsString {
impl TryFrom<Params> for phc::ParamsString {
type Error = Error;
fn try_from(params: Params) -> Result<ParamsString, Error> {
fn try_from(params: Params) -> Result<phc::ParamsString, Error> {
Self::try_from(&params)
}
}
#[cfg(feature = "phc")]
impl TryFrom<&Params> for ParamsString {
impl TryFrom<&Params> for phc::ParamsString {
type Error = Error;
fn try_from(input: &Params) -> Result<ParamsString, Error> {
let mut output = ParamsString::new();
fn try_from(input: &Params) -> Result<phc::ParamsString, Error> {
let mut output = phc::ParamsString::new();
for (name, value) in [
("ln", input.log_n as Decimal),
("ln", input.log_n as phc::Decimal),
("r", input.r),
("p", input.p),
] {

View File

@@ -21,6 +21,7 @@ sha2 = { version = "0.11.0-rc.3", default-features = false }
subtle = { version = "2", default-features = false }
# optional dependencies
kdf = { version = "0.1.0-pre.1", optional = true }
mcf = { version = "0.6.0-rc.2", optional = true, default-features = false, features = ["alloc", "base64"] }
password-hash = { version = "0.6.0-rc.9", optional = true, default-features = false }
@@ -30,6 +31,7 @@ hex-literal = "1"
[features]
default = ["getrandom"]
getrandom = ["password-hash", "password-hash/getrandom"]
kdf = ["dep:kdf"]
rand_core = ["password-hash/rand_core"]
password-hash = ["dep:mcf", "dep:password-hash"]

View File

@@ -37,6 +37,13 @@ impl From<TryFromIntError> for Error {
}
}
#[cfg(feature = "kdf")]
impl From<Error> for kdf::Error {
fn from(_: Error) -> Self {
kdf::Error
}
}
#[cfg(feature = "password-hash")]
impl From<Error> for password_hash::Error {
fn from(err: Error) -> Self {

View File

@@ -81,9 +81,11 @@ pub use crate::{
params::Params,
};
#[cfg(feature = "kdf")]
pub use kdf::{self, Kdf, Pbkdf};
#[cfg(feature = "password-hash")]
pub use {
crate::mcf::{PasswordHash, PasswordHashRef, Yescrypt},
crate::mcf::{PasswordHash, PasswordHashRef},
password_hash::{self, CustomizedPasswordHasher, PasswordHasher, PasswordVerifier},
};
@@ -217,3 +219,41 @@ fn yescrypt_body(
Ok(())
}
/// yescrypt password hashing type which can produce and verify strings in Modular Crypt Format
/// (MCF) which begin with `$y$`
///
/// This type impls traits from the [`password-hash`][`password_hash`] crate, notably the
/// [`PasswordHasher`], [`PasswordVerifier`], and [`CustomizedPasswordHasher`] traits.
///
/// See the toplevel documentation for a code example.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Yescrypt {
/// Default parameters to use when hashing passwords.
params: Params,
}
impl Yescrypt {
/// Hash password into the given output buffer using the configured params.
pub fn hash_password_into(&self, password: &[u8], salt: &[u8], out: &mut [u8]) -> Result<()> {
yescrypt(password, salt, &self.params, out)?;
Ok(())
}
}
impl From<Params> for Yescrypt {
fn from(params: Params) -> Self {
Self { params }
}
}
#[cfg(feature = "kdf")]
impl Kdf for Yescrypt {
fn derive_key(&self, password: &[u8], salt: &[u8], out: &mut [u8]) -> kdf::Result<()> {
self.hash_password_into(password, salt, out)?;
Ok(())
}
}
#[cfg(feature = "kdf")]
impl Pbkdf for Yescrypt {}

View File

@@ -2,7 +2,7 @@
pub use mcf::{PasswordHash, PasswordHashRef};
use crate::{Params, yescrypt};
use crate::{Params, Yescrypt, yescrypt};
use alloc::vec;
use mcf::Base64;
use password_hash::{
@@ -16,25 +16,6 @@ const YESCRYPT_MCF_ID: &str = "y";
/// Base64 variant used by yescrypt.
const YESCRYPT_BASE64: Base64 = Base64::Crypt;
/// yescrypt password hashing type which can produce and verify strings in Modular Crypt Format
/// (MCF) which begin with `$y$`
///
/// This type impls traits from the [`password-hash`][`password_hash`] crate, notably the
/// [`PasswordHasher`], [`PasswordVerifier`], and [`CustomizedPasswordHasher`] traits.
///
/// See the toplevel documentation for a code example.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Yescrypt {
/// Default parameters to use when hashing passwords.
params: Params,
}
impl From<Params> for Yescrypt {
fn from(params: Params) -> Self {
Self { params }
}
}
impl CustomizedPasswordHasher<PasswordHash> for Yescrypt {
type Params = Params;