use hmac::Hmac;
use sha2::{Sha256, Sha512};
use zeroize::{Zeroize, ZeroizeOnDrop};
type HmacSha256 = Hmac<Sha256>;
type HmacSha512 = Hmac<Sha512>;
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct DerivedKey {
key: Vec<u8>,
}
impl DerivedKey {
pub fn from_bytes(bytes: Vec<u8>) -> Self {
Self { key: bytes }
}
pub fn as_bytes(&self) -> &[u8] {
&self.key
}
pub fn len(&self) -> usize {
self.key.len()
}
pub fn is_empty(&self) -> bool {
self.key.is_empty()
}
}
pub struct Pbkdf2;
impl Pbkdf2 {
pub fn derive_key_sha256(
password: &[u8],
salt: &[u8],
iterations: u32,
key_length: usize,
) -> DerivedKey {
let mut derived = vec![0u8; key_length];
pbkdf2::pbkdf2::<HmacSha256>(password, salt, iterations, &mut derived)
.expect("PBKDF2 derivation failed");
DerivedKey::from_bytes(derived)
}
pub fn derive_key_sha512(
password: &[u8],
salt: &[u8],
iterations: u32,
key_length: usize,
) -> DerivedKey {
let mut derived = vec![0u8; key_length];
pbkdf2::pbkdf2::<HmacSha512>(password, salt, iterations, &mut derived)
.expect("PBKDF2 derivation failed");
DerivedKey::from_bytes(derived)
}
}
pub struct Hkdf;
impl Hkdf {
pub fn derive_key(
input_key_material: &[u8],
salt: &[u8],
info: &[u8],
output_length: usize,
) -> DerivedKey {
use hkdf::Hkdf as HkdfImpl;
let hk = HkdfImpl::<Sha256>::new(Some(salt), input_key_material);
let mut okm = vec![0u8; output_length];
hk.expand(info, &mut okm).expect("HKDF expand failed");
DerivedKey::from_bytes(okm)
}
pub fn derive_multiple_keys(
input_key_material: &[u8],
salt: &[u8],
contexts: &[&[u8]],
key_length: usize,
) -> Vec<DerivedKey> {
contexts
.iter()
.map(|context| Self::derive_key(input_key_material, salt, context, key_length))
.collect()
}
}
pub struct PasswordStrength;
impl PasswordStrength {
pub fn check(password: &str) -> (u8, Vec<String>) {
let mut score = 0u8;
let mut feedback = Vec::new();
if password.len() >= 12 {
score += 1;
} else {
feedback.push("Password should be at least 12 characters".to_string());
}
if password.chars().any(|c| c.is_uppercase()) {
score += 1;
} else {
feedback.push("Add uppercase letters".to_string());
}
if password.chars().any(|c| c.is_lowercase()) {
score += 1;
} else {
feedback.push("Add lowercase letters".to_string());
}
if password.chars().any(|c| c.is_numeric()) {
score += 1;
} else {
feedback.push("Add numbers".to_string());
}
if password.chars().any(|c| !c.is_alphanumeric()) {
score += 1;
} else {
feedback.push("Add special characters".to_string());
}
let common_passwords = [
"password", "123456", "qwerty", "admin", "letmein", "welcome", "monkey", "dragon",
"master", "sunshine", "princess", "football",
];
if common_passwords
.iter()
.any(|&common| password.to_lowercase().contains(common))
{
score = score.saturating_sub(2);
feedback.push("Avoid common passwords".to_string());
}
if Self::has_sequential_chars(password) {
score = score.saturating_sub(1);
feedback.push("Avoid sequential characters (abc, 123, etc.)".to_string());
}
(score.min(4), feedback)
}
fn has_sequential_chars(password: &str) -> bool {
let chars: Vec<char> = password.chars().collect();
for window in chars.windows(3) {
if window.len() == 3 {
let a = window[0] as i32;
let b = window[1] as i32;
let c = window[2] as i32;
if (b == a + 1 && c == b + 1) || (b == a - 1 && c == b - 1) {
return true;
}
}
}
false
}
pub fn strength_description(score: u8) -> &'static str {
match score {
0 => "Very Weak",
1 => "Weak",
2 => "Fair",
3 => "Strong",
4 | 5 => "Very Strong",
_ => "Unknown",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pbkdf2_derivation() {
let password = b"my_secure_password";
let salt = b"random_salt_12345";
let iterations = 10000;
let key_length = 32;
let key = Pbkdf2::derive_key_sha256(password, salt, iterations, key_length);
assert_eq!(key.len(), key_length);
}
#[test]
fn test_pbkdf2_deterministic() {
let password = b"test_password";
let salt = b"test_salt";
let iterations = 1000;
let key1 = Pbkdf2::derive_key_sha256(password, salt, iterations, 32);
let key2 = Pbkdf2::derive_key_sha256(password, salt, iterations, 32);
assert_eq!(key1.as_bytes(), key2.as_bytes());
}
#[test]
fn test_hkdf_derivation() {
let ikm = b"input_key_material";
let salt = b"salt";
let info = b"application_context";
let key_length = 32;
let key = Hkdf::derive_key(ikm, salt, info, key_length);
assert_eq!(key.len(), key_length);
}
#[test]
fn test_hkdf_multiple_keys() {
let ikm = b"shared_secret";
let salt = b"salt";
let contexts = vec![b"encryption".as_slice(), b"authentication".as_slice()];
let keys = Hkdf::derive_multiple_keys(ikm, salt, &contexts, 32);
assert_eq!(keys.len(), 2);
assert_ne!(keys[0].as_bytes(), keys[1].as_bytes());
}
#[test]
fn test_password_strength_weak() {
let (score, _feedback) = PasswordStrength::check("pass");
assert!(score <= 2);
}
#[test]
fn test_password_strength_strong() {
let (score, feedback) = PasswordStrength::check("MyStr0ng!P@ssw0rd2024");
assert_eq!(score, 4); assert!(feedback.is_empty());
}
#[test]
fn test_password_strength_common() {
let (score, feedback) = PasswordStrength::check("password123");
assert!(score < 3);
assert!(feedback.iter().any(|f| f.contains("common")));
}
#[test]
fn test_password_strength_sequential() {
let (_score, feedback) = PasswordStrength::check("abc123xyz");
assert!(feedback.iter().any(|f| f.contains("sequential")));
}
#[test]
fn test_strength_descriptions() {
assert_eq!(PasswordStrength::strength_description(0), "Very Weak");
assert_eq!(PasswordStrength::strength_description(2), "Fair");
assert_eq!(PasswordStrength::strength_description(4), "Very Strong");
}
#[test]
fn test_derived_key_zeroization() {
let password = b"test";
let salt = b"salt";
{
let _key = Pbkdf2::derive_key_sha256(password, salt, 1000, 32);
}
}
}