use sha2::Sha256;
use crate::contract::{Identified, PasswordBasedKdf};
use crate::error::{Error, Result};
const MAX_OUTPUT_LEN: usize = u32::MAX as usize;
#[derive(Debug, Clone, Copy)]
pub struct Pbkdf2Sha256 {
iterations: u32,
}
impl Pbkdf2Sha256 {
pub const IDENTIFIER: &'static str = "pbkdf2-sha256";
pub const DEFAULT_ITERATIONS: u32 = 310_000;
pub fn new(iterations: u32) -> Result<Self> {
if iterations == 0 {
return Err(Error::InvalidIterations);
}
Ok(Self { iterations })
}
pub fn with_default_iterations() -> Self {
Self {
iterations: Self::DEFAULT_ITERATIONS,
}
}
pub fn iterations(&self) -> u32 {
self.iterations
}
}
impl Default for Pbkdf2Sha256 {
fn default() -> Self {
Self::with_default_iterations()
}
}
impl Identified for Pbkdf2Sha256 {
fn identifier(&self) -> &str {
Self::IDENTIFIER
}
}
impl PasswordBasedKdf for Pbkdf2Sha256 {
fn derive_from_password(&self, password: &[u8], salt: &[u8], length: usize) -> Result<Vec<u8>> {
if length == 0 || length > MAX_OUTPUT_LEN {
return Err(Error::InvalidOutputLength {
requested: length,
max: MAX_OUTPUT_LEN,
});
}
let mut output = vec![0u8; length];
pbkdf2::pbkdf2_hmac::<Sha256>(password, salt, self.iterations, &mut output);
Ok(output)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_known_vector() {
let kdf = Pbkdf2Sha256::new(1).unwrap();
let derived = kdf.derive_from_password(b"password", b"salt", 32).unwrap();
assert_eq!(
crate::internal::hex_encode(&derived),
"120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"
);
}
#[test]
fn same_inputs_same_output_and_length_validation() {
let kdf = Pbkdf2Sha256::with_default_iterations();
assert_eq!(kdf.iterations(), 310_000);
let first = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
let second = kdf.derive_from_password(b"pw", b"salt", 16).unwrap();
assert_eq!(first, second);
assert_eq!(
kdf.derive_from_password(b"pw", b"salt", 0).unwrap_err(),
Error::InvalidOutputLength {
requested: 0,
max: MAX_OUTPUT_LEN
}
);
assert_eq!(
kdf.derive_from_password(b"pw", b"salt", usize::MAX / 2)
.unwrap_err(),
Error::InvalidOutputLength {
requested: usize::MAX / 2,
max: MAX_OUTPUT_LEN
}
);
assert_eq!(
kdf.derive_from_password(b"pw", b"salt", MAX_OUTPUT_LEN + 1)
.unwrap_err(),
Error::InvalidOutputLength {
requested: MAX_OUTPUT_LEN + 1,
max: MAX_OUTPUT_LEN
}
);
}
#[test]
fn zero_iterations_is_rejected() {
assert_eq!(Pbkdf2Sha256::new(0).unwrap_err(), Error::InvalidIterations);
}
}