use crate::error::{Error, Result};
use crate::models::hash_algorithm::HashingAlgorithm;
use serde::{Deserialize, Serialize};
pub const DEFAULT_OUTPUT_LEN: usize = 32;
#[derive(
Clone,
Copy,
Debug,
Default,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
)]
pub enum Prf {
#[default]
Sha256,
Sha512,
}
impl Prf {
#[must_use]
pub const fn phc_id(self) -> &'static str {
match self {
Self::Sha256 => "pbkdf2-sha256",
Self::Sha512 => "pbkdf2-sha512",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Pbkdf2Params {
pub prf: Prf,
pub iterations: u32,
pub dk_len: usize,
}
impl Default for Pbkdf2Params {
fn default() -> Self {
Self::owasp_minimum_2025()
}
}
impl Pbkdf2Params {
#[must_use]
pub const fn owasp_minimum_2025() -> Self {
Self {
prf: Prf::Sha256,
iterations: 600_000,
dk_len: DEFAULT_OUTPUT_LEN,
}
}
#[must_use]
pub const fn owasp_minimum_2025_sha512() -> Self {
Self {
prf: Prf::Sha512,
iterations: 210_000,
dk_len: DEFAULT_OUTPUT_LEN,
}
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
)]
pub struct Pbkdf2;
impl HashingAlgorithm for Pbkdf2 {
fn hash_password(password: &str, salt: &str) -> Result<Vec<u8>> {
Self::hash_with(
password.as_bytes(),
salt.as_bytes(),
Pbkdf2Params::default(),
)
}
}
impl Pbkdf2 {
pub fn hash_with(
password: &[u8],
salt: &[u8],
params: Pbkdf2Params,
) -> Result<Vec<u8>> {
if params.iterations < 1 {
return Err(Error::InvalidParameter(
"iterations must be >= 1".into(),
));
}
if params.dk_len == 0 {
return Err(Error::InvalidParameter(
"dk_len must be > 0".into(),
));
}
#[cfg(feature = "fips")]
{
aws_lc::derive(password, salt, params)
}
#[cfg(not(feature = "fips"))]
{
rust_crypto::derive(password, salt, params)
}
}
}
#[cfg(feature = "fips")]
mod aws_lc {
use super::{Pbkdf2Params, Prf};
use crate::error::{Error, HashingErrorKind, Result};
use hsh_backend_awslc::{pbkdf2_derive, Prf as AwslcPrf};
pub(super) fn derive(
password: &[u8],
salt: &[u8],
params: Pbkdf2Params,
) -> Result<Vec<u8>> {
let prf = match params.prf {
Prf::Sha256 => AwslcPrf::Sha256,
Prf::Sha512 => AwslcPrf::Sha512,
};
pbkdf2_derive(
password,
salt,
prf,
params.iterations,
params.dk_len,
)
.map_err(|e| {
Error::hashing(HashingErrorKind::Pbkdf2, e.to_string())
})
}
}
#[allow(dead_code)]
mod rust_crypto {
use super::{Pbkdf2Params, Prf};
use crate::error::{Error, HashingErrorKind, Result};
use hmac::Hmac;
use sha2::{Sha256, Sha512};
pub(super) fn derive(
password: &[u8],
salt: &[u8],
params: Pbkdf2Params,
) -> Result<Vec<u8>> {
let mut out = vec![0u8; params.dk_len];
match params.prf {
Prf::Sha256 => pbkdf2::pbkdf2::<Hmac<Sha256>>(
password,
salt,
params.iterations,
&mut out,
)
.map_err(|e| {
Error::hashing(HashingErrorKind::Pbkdf2, e.to_string())
})?,
Prf::Sha512 => pbkdf2::pbkdf2::<Hmac<Sha512>>(
password,
salt,
params.iterations,
&mut out,
)
.map_err(|e| {
Error::hashing(HashingErrorKind::Pbkdf2, e.to_string())
})?,
}
Ok(out)
}
}