rust_crypto_utils/
keyderivation.rs1use hmac::{Hmac, Mac};
4use sha2::{Sha256, Sha512};
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7type HmacSha256 = Hmac<Sha256>;
8type HmacSha512 = Hmac<Sha512>;
9
10#[derive(Zeroize, ZeroizeOnDrop)]
12pub struct DerivedKey {
13 key: Vec<u8>,
14}
15
16impl DerivedKey {
17 pub fn from_bytes(bytes: Vec<u8>) -> Self {
19 Self { key: bytes }
20 }
21
22 pub fn as_bytes(&self) -> &[u8] {
24 &self.key
25 }
26
27 pub fn len(&self) -> usize {
29 self.key.len()
30 }
31
32 pub fn is_empty(&self) -> bool {
34 self.key.is_empty()
35 }
36}
37
38pub struct Pbkdf2;
40
41impl Pbkdf2 {
42 pub fn derive_key_sha256(
50 password: &[u8],
51 salt: &[u8],
52 iterations: u32,
53 key_length: usize,
54 ) -> DerivedKey {
55 let mut derived = vec![0u8; key_length];
56 pbkdf2::pbkdf2::<HmacSha256>(password, salt, iterations, &mut derived)
57 .expect("PBKDF2 derivation failed");
58 DerivedKey::from_bytes(derived)
59 }
60
61 pub fn derive_key_sha512(
63 password: &[u8],
64 salt: &[u8],
65 iterations: u32,
66 key_length: usize,
67 ) -> DerivedKey {
68 let mut derived = vec![0u8; key_length];
69 pbkdf2::pbkdf2::<HmacSha512>(password, salt, iterations, &mut derived)
70 .expect("PBKDF2 derivation failed");
71 DerivedKey::from_bytes(derived)
72 }
73}
74
75pub struct Hkdf;
77
78impl Hkdf {
79 pub fn derive_key(
87 input_key_material: &[u8],
88 salt: &[u8],
89 info: &[u8],
90 output_length: usize,
91 ) -> DerivedKey {
92 use hkdf::Hkdf as HkdfImpl;
93
94 let hk = HkdfImpl::<Sha256>::new(Some(salt), input_key_material);
95 let mut okm = vec![0u8; output_length];
96 hk.expand(info, &mut okm)
97 .expect("HKDF expand failed");
98
99 DerivedKey::from_bytes(okm)
100 }
101
102 pub fn derive_multiple_keys(
104 input_key_material: &[u8],
105 salt: &[u8],
106 contexts: &[&[u8]],
107 key_length: usize,
108 ) -> Vec<DerivedKey> {
109 contexts
110 .iter()
111 .map(|context| Self::derive_key(input_key_material, salt, context, key_length))
112 .collect()
113 }
114}
115
116pub struct PasswordStrength;
118
119impl PasswordStrength {
120 pub fn check(password: &str) -> (u8, Vec<String>) {
123 let mut score = 0u8;
124 let mut feedback = Vec::new();
125
126 if password.len() >= 12 {
128 score += 1;
129 } else {
130 feedback.push("Password should be at least 12 characters".to_string());
131 }
132
133 if password.chars().any(|c| c.is_uppercase()) {
135 score += 1;
136 } else {
137 feedback.push("Add uppercase letters".to_string());
138 }
139
140 if password.chars().any(|c| c.is_lowercase()) {
142 score += 1;
143 } else {
144 feedback.push("Add lowercase letters".to_string());
145 }
146
147 if password.chars().any(|c| c.is_numeric()) {
149 score += 1;
150 } else {
151 feedback.push("Add numbers".to_string());
152 }
153
154 if password.chars().any(|c| !c.is_alphanumeric()) {
156 score += 1;
157 } else {
158 feedback.push("Add special characters".to_string());
159 }
160
161 let common_passwords = [
163 "password", "123456", "qwerty", "admin", "letmein", "welcome",
164 "monkey", "dragon", "master", "sunshine", "princess", "football"
165 ];
166 if common_passwords.iter().any(|&common| password.to_lowercase().contains(common)) {
167 score = score.saturating_sub(2);
168 feedback.push("Avoid common passwords".to_string());
169 }
170
171 if Self::has_sequential_chars(password) {
173 score = score.saturating_sub(1);
174 feedback.push("Avoid sequential characters (abc, 123, etc.)".to_string());
175 }
176
177 (score.min(4), feedback)
178 }
179
180 fn has_sequential_chars(password: &str) -> bool {
181 let chars: Vec<char> = password.chars().collect();
182 for window in chars.windows(3) {
183 if window.len() == 3 {
184 let a = window[0] as i32;
185 let b = window[1] as i32;
186 let c = window[2] as i32;
187 if (b == a + 1 && c == b + 1) || (b == a - 1 && c == b - 1) {
188 return true;
189 }
190 }
191 }
192 false
193 }
194
195 pub fn strength_description(score: u8) -> &'static str {
197 match score {
198 0 => "Very Weak",
199 1 => "Weak",
200 2 => "Fair",
201 3 => "Strong",
202 4 | 5 => "Very Strong",
203 _ => "Unknown",
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_pbkdf2_derivation() {
214 let password = b"my_secure_password";
215 let salt = b"random_salt_12345";
216 let iterations = 10000;
217 let key_length = 32;
218
219 let key = Pbkdf2::derive_key_sha256(password, salt, iterations, key_length);
220 assert_eq!(key.len(), key_length);
221 }
222
223 #[test]
224 fn test_pbkdf2_deterministic() {
225 let password = b"test_password";
226 let salt = b"test_salt";
227 let iterations = 1000;
228
229 let key1 = Pbkdf2::derive_key_sha256(password, salt, iterations, 32);
230 let key2 = Pbkdf2::derive_key_sha256(password, salt, iterations, 32);
231
232 assert_eq!(key1.as_bytes(), key2.as_bytes());
233 }
234
235 #[test]
236 fn test_hkdf_derivation() {
237 let ikm = b"input_key_material";
238 let salt = b"salt";
239 let info = b"application_context";
240 let key_length = 32;
241
242 let key = Hkdf::derive_key(ikm, salt, info, key_length);
243 assert_eq!(key.len(), key_length);
244 }
245
246 #[test]
247 fn test_hkdf_multiple_keys() {
248 let ikm = b"shared_secret";
249 let salt = b"salt";
250 let contexts = vec![b"encryption".as_slice(), b"authentication".as_slice()];
251
252 let keys = Hkdf::derive_multiple_keys(ikm, salt, &contexts, 32);
253 assert_eq!(keys.len(), 2);
254 assert_ne!(keys[0].as_bytes(), keys[1].as_bytes());
255 }
256
257 #[test]
258 fn test_password_strength_weak() {
259 let (score, _feedback) = PasswordStrength::check("pass");
260 assert!(score <= 2);
261 }
262
263 #[test]
264 fn test_password_strength_strong() {
265 let (score, feedback) = PasswordStrength::check("MyStr0ng!P@ssw0rd2024");
266 assert_eq!(score, 5);
267 assert!(feedback.is_empty());
268 }
269
270 #[test]
271 fn test_password_strength_common() {
272 let (score, feedback) = PasswordStrength::check("password123");
273 assert!(score < 3);
274 assert!(feedback.iter().any(|f| f.contains("common")));
275 }
276
277 #[test]
278 fn test_password_strength_sequential() {
279 let (score, feedback) = PasswordStrength::check("abc123xyz");
280 assert!(feedback.iter().any(|f| f.contains("sequential")));
281 }
282
283 #[test]
284 fn test_strength_descriptions() {
285 assert_eq!(PasswordStrength::strength_description(0), "Very Weak");
286 assert_eq!(PasswordStrength::strength_description(2), "Fair");
287 assert_eq!(PasswordStrength::strength_description(4), "Very Strong");
288 }
289
290 #[test]
291 fn test_derived_key_zeroization() {
292 let password = b"test";
293 let salt = b"salt";
294 {
295 let _key = Pbkdf2::derive_key_sha256(password, salt, 1000, 32);
296 }
298 }
300}