#![cfg_attr(not(feature = "std"), no_std)]
use crate::error::{validate, Error, Result};
use crate::hash::HashFunction;
use crate::kdf::common::constant_time_eq;
use crate::kdf::{KdfAlgorithm, KdfOperation, PasswordHash, SecurityLevel};
use crate::kdf::{KeyDerivationFunction, ParamProvider, PasswordHashFunction};
use crate::mac::hmac::Hmac;
use crate::types::salt::Pbkdf2Compatible;
use crate::types::{ByteSerializable, Salt, SecretBytes};
use dcrypt_common::security::SecretVec;
#[cfg(feature = "std")]
use std::collections::BTreeMap;
#[cfg(feature = "std")]
use std::string::String;
#[cfg(feature = "std")]
use std::time::{Duration, Instant};
#[cfg(feature = "std")]
use std::vec::Vec;
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::collections::BTreeMap;
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::string::String;
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use core::time::Duration;
use rand::{CryptoRng, RngCore};
use std::marker::PhantomData;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
pub enum Pbkdf2Algorithm<H: HashFunction> {
_Hash(PhantomData<H>),
}
impl<H: HashFunction> KdfAlgorithm for Pbkdf2Algorithm<H> {
const MIN_SALT_SIZE: usize = 16;
const DEFAULT_OUTPUT_SIZE: usize = 32;
const ALGORITHM_ID: &'static str = "PBKDF2";
fn name() -> String {
format!("{}-{}", Self::ALGORITHM_ID, H::name())
}
fn security_level() -> SecurityLevel {
match H::output_size() * 8 {
bits if bits >= 512 => SecurityLevel::L128, bits if bits >= 384 => SecurityLevel::L128,
bits if bits >= 256 => SecurityLevel::L128,
bits => SecurityLevel::Custom(bits as u32 / 2),
}
}
}
#[derive(Clone, Debug, Zeroize)]
pub struct Pbkdf2Params<const S: usize = 16> {
pub salt: Salt<S>,
pub iterations: u32,
pub key_length: usize,
}
impl<const S: usize> Default for Pbkdf2Params<S>
where
Salt<S>: Pbkdf2Compatible,
{
fn default() -> Self {
Self {
salt: Salt::<S>::zeroed(), iterations: 600_000, key_length: 32, }
}
}
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct Pbkdf2<H: HashFunction + Clone, const S: usize = 16> {
_hash_type: PhantomData<H>,
params: Pbkdf2Params<S>,
}
pub struct Pbkdf2Builder<'a, H: HashFunction + Clone, const S: usize = 16> {
kdf: &'a Pbkdf2<H, S>,
ikm: Option<&'a [u8]>,
salt: Option<&'a [u8]>,
iterations: u32,
length: usize,
}
impl<H: HashFunction + Clone, const S: usize> Pbkdf2Builder<'_, H, S> {
pub fn with_iterations(mut self, iterations: u32) -> Self {
self.iterations = iterations;
self
}
}
impl<'a, H: HashFunction + Clone, const S: usize> KdfOperation<'a, Pbkdf2Algorithm<H>>
for Pbkdf2Builder<'a, H, S>
where
Salt<S>: Pbkdf2Compatible,
{
fn with_ikm(mut self, ikm: &'a [u8]) -> Self {
self.ikm = Some(ikm);
self
}
fn with_salt(mut self, salt: &'a [u8]) -> Self {
self.salt = Some(salt);
self
}
fn with_info(self, _info: &'a [u8]) -> Self {
self
}
fn with_output_length(mut self, length: usize) -> Self {
self.length = length;
self
}
fn derive(self) -> Result<Vec<u8>> {
let ikm = self.ikm.ok_or_else(|| {
Error::param("input_keying_material", "Input keying material is required")
})?;
let salt = match self.salt {
Some(s) => s,
None => self.kdf.params.salt.as_ref(),
};
Pbkdf2::<H, S>::pbkdf2_secure(ikm, salt, self.iterations, self.length)
}
fn derive_array<const N: usize>(self) -> Result<[u8; N]> {
validate::length("PBKDF2 output", self.length, N)?;
let vec = self.derive()?;
let mut array = [0u8; N];
array.copy_from_slice(&vec);
Ok(array)
}
}
impl<H: HashFunction + Clone, const S: usize> Pbkdf2<H, S> {
pub fn pbkdf2(
password: &[u8],
salt: &[u8],
iterations: u32,
key_length: usize,
) -> Result<Zeroizing<Vec<u8>>> {
let secure_password = SecretVec::from_slice(password);
Self::pbkdf2_internal(&secure_password, salt, iterations, key_length)
}
pub fn pbkdf2_secure(
password: &[u8],
salt: &[u8],
iterations: u32,
key_length: usize,
) -> Result<Vec<u8>> {
let result = Self::pbkdf2(password, salt, iterations, key_length)?;
Ok(result.to_vec())
}
fn pbkdf2_internal(
password: &SecretVec,
salt: &[u8],
iterations: u32,
key_length: usize,
) -> Result<Zeroizing<Vec<u8>>> {
validate::parameter(
iterations > 0,
"iterations",
"PBKDF2 iteration count must be > 0",
)?;
validate::parameter(
key_length > 0,
"key_length",
"PBKDF2 output length must be > 0",
)?;
let hash_len = H::output_size();
let block_count = key_length.div_ceil(hash_len);
if block_count > 0xFFFFFFFF {
return Err(Error::Length {
context: "PBKDF2 output length",
expected: 0xFFFFFFFF * hash_len,
actual: key_length,
});
}
let mut result = Zeroizing::new(Vec::with_capacity(key_length));
for block_index in 1..=block_count {
let block =
Self::pbkdf2_f::<H>(password.as_ref(), salt, iterations, block_index as u32)?;
let to_copy = if block_index == block_count {
let remainder = key_length % hash_len;
if remainder == 0 {
hash_len
} else {
remainder
}
} else {
hash_len
};
result.extend_from_slice(&block[..to_copy]);
}
Ok(result)
}
fn pbkdf2_f<T: HashFunction + Clone>(
password: &[u8],
salt: &[u8],
iterations: u32,
block_index: u32,
) -> Result<Zeroizing<Vec<u8>>> {
let mut hmac = Hmac::<T>::new(password)?;
hmac.update(salt)?;
hmac.update(&block_index.to_be_bytes())?;
let result = Zeroizing::new(hmac.finalize()?);
let mut prev = result.clone();
let mut output = Zeroizing::new(result.to_vec());
for _ in 1..iterations {
let mut hmac = Hmac::<T>::new(password)?;
hmac.update(&prev)?;
prev = Zeroizing::new(hmac.finalize()?);
for i in 0..output.len() {
output[i] ^= prev[i];
}
}
Ok(output)
}
}
impl<H: HashFunction + Clone, const S: usize> ParamProvider for Pbkdf2<H, S> {
type Params = Pbkdf2Params<S>;
fn with_params(params: Self::Params) -> Self {
Self {
_hash_type: PhantomData,
params,
}
}
fn params(&self) -> &Self::Params {
&self.params
}
fn set_params(&mut self, params: Self::Params) {
self.params = params;
}
}
impl<H: HashFunction + Clone, const S: usize> KeyDerivationFunction for Pbkdf2<H, S>
where
Salt<S>: Pbkdf2Compatible,
{
type Algorithm = Pbkdf2Algorithm<H>;
type Salt = Salt<S>;
fn new() -> Self {
Self {
_hash_type: PhantomData,
params: Pbkdf2Params::default(),
}
}
#[cfg(feature = "alloc")]
fn derive_key(
&self,
input: &[u8],
salt: Option<&[u8]>,
_info: Option<&[u8]>,
length: usize,
) -> Result<Vec<u8>> {
let effective_salt = match salt {
Some(s) => s,
None => self.params.salt.as_ref(),
};
let effective_length = if length > 0 {
length
} else {
self.params.key_length
};
Self::pbkdf2_secure(
input,
effective_salt,
self.params.iterations,
effective_length,
)
}
fn builder(&self) -> impl KdfOperation<'_, Self::Algorithm> {
Pbkdf2Builder {
kdf: self,
ikm: None,
salt: None,
iterations: self.params.iterations,
length: self.params.key_length,
}
}
fn generate_salt<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Salt {
Salt::random_with_size(rng, Self::Algorithm::MIN_SALT_SIZE).expect("Salt generation failed")
}
fn security_level() -> SecurityLevel {
Self::Algorithm::security_level()
}
}
#[cfg(feature = "std")]
impl<H: HashFunction + Clone, const S: usize> PasswordHashFunction for Pbkdf2<H, S>
where
Salt<S>: Pbkdf2Compatible,
{
type Password = SecretBytes<32>;
fn hash_password(&self, password: &Self::Password) -> Result<PasswordHash> {
let hash = Self::pbkdf2(
password.as_ref(),
self.params.salt.as_ref(),
self.params.iterations,
self.params.key_length,
)?;
let mut params = BTreeMap::new();
params.insert("i".to_string(), self.params.iterations.to_string());
Ok(PasswordHash {
algorithm: format!("pbkdf2-{}", H::name().to_lowercase()),
params,
salt: Zeroizing::new(self.params.salt.to_bytes()),
hash,
})
}
fn verify(&self, password: &Self::Password, hash: &PasswordHash) -> Result<bool> {
let expected_alg = format!("pbkdf2-{}", H::name().to_lowercase());
validate::parameter(
hash.algorithm == expected_alg,
"algorithm",
"Algorithm mismatch",
)?;
let iterations = match hash.param("i") {
Some(i) => i
.parse::<u32>()
.map_err(|_| Error::param("iterations", "Invalid iterations parameter"))?,
None => return Err(Error::param("iterations", "Missing iterations parameter")),
};
let derived = Self::pbkdf2(password.as_ref(), &hash.salt, iterations, hash.hash.len())?;
Ok(constant_time_eq(&derived, &hash.hash))
}
fn benchmark(&self) -> Duration {
let start = Instant::now();
let password = SecretBytes::new([0u8; 32]);
match self.hash_password(&password) {
Ok(_) => {}
Err(_) => {
}
}
start.elapsed()
}
fn recommended_params(target_duration: Duration) -> Self::Params {
let mut params = Pbkdf2Params::default();
let instance = Self::with_params(params.clone());
let current_duration = instance.benchmark();
let ratio = target_duration.as_secs_f64() / current_duration.as_secs_f64();
params.iterations = (params.iterations as f64 * ratio) as u32;
params.iterations = core::cmp::max(params.iterations, 10_000);
params
}
}
#[cfg(test)]
mod tests;