rust_crypto_utils/
lib.rs

1//! # Rust Crypto Utils
2//!
3//! Production-ready, memory-safe cryptographic utilities for financial systems and secure applications.
4//!
5//! ## Features
6//!
7//! - **Memory Safety**: Automatic zeroization of sensitive data
8//! - **Secure Password Hashing**: Argon2id with configurable parameters
9//! - **AES-256-GCM Encryption**: Authenticated encryption with associated data
10//! - **Key Derivation**: PBKDF2 and HKDF (NIST SP 800-132, RFC 5869)
11//! - **Digital Signatures**: Ed25519 and HMAC-SHA256
12//! - **Key Management**: Secure key storage with rotation policies
13//! - **Secure Random Generation**: Cryptographically secure random number generation
14//!
15//! ## Alignment with Federal Guidance
16//!
17//! Implements cryptographic best practices recommended by NIST and aligns with
18//! 2024 CISA/FBI guidance for memory-safe cryptographic implementations.
19
20pub mod keyderivation;
21pub mod signatures;
22pub mod keymanagement;
23
24pub use keyderivation::{DerivedKey, Hkdf, Pbkdf2, PasswordStrength};
25pub use signatures::{Ed25519KeyPair, Ed25519PublicKey, HmacKey, SignatureSuite};
26pub use keymanagement::{KeyMetadata, KeyStore, RotationPolicy};
27
28use aes_gcm::{
29    aead::{Aead, KeyInit, OsRng},
30    Aes256Gcm, Nonce,
31};
32use argon2::{
33    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
34    Argon2, Algorithm, Version, Params,
35};
36use hmac::{Hmac, Mac};
37use rand::RngCore;
38use sha2::Sha256;
39use thiserror::Error;
40use zeroize::{Zeroize, ZeroizeOnDrop};
41
42type HmacSha256 = Hmac<Sha256>;
43
44/// Cryptographic errors
45#[derive(Error, Debug)]
46pub enum CryptoError {
47    #[error("Password hashing failed: {0}")]
48    HashingError(String),
49
50    #[error("Password verification failed")]
51    VerificationError,
52
53    #[error("Encryption failed: {0}")]
54    EncryptionError(String),
55
56    #[error("Decryption failed: {0}")]
57    DecryptionError(String),
58
59    #[error("Invalid key length")]
60    InvalidKeyLength,
61
62    #[error("HMAC generation failed: {0}")]
63    HmacError(String),
64
65    #[error("Weak password: {0}")]
66    WeakPassword(String),
67}
68
69/// Secure password with automatic zeroization
70#[derive(Zeroize, ZeroizeOnDrop)]
71pub struct SecurePassword {
72    password: Vec<u8>,
73}
74
75impl SecurePassword {
76    /// Create a new secure password
77    pub fn new(password: impl Into<Vec<u8>>) -> Self {
78        Self {
79            password: password.into(),
80        }
81    }
82
83    /// Get password bytes
84    pub fn as_bytes(&self) -> &[u8] {
85        &self.password
86    }
87}
88
89/// Secure encryption key with automatic zeroization
90#[derive(Zeroize, ZeroizeOnDrop)]
91pub struct SecureKey {
92    key: Vec<u8>,
93}
94
95impl SecureKey {
96    /// Create a new secure key from bytes
97    pub fn new(key: Vec<u8>) -> Result<Self, CryptoError> {
98        if key.len() != 32 {
99            return Err(CryptoError::InvalidKeyLength);
100        }
101        Ok(Self { key })
102    }
103
104    /// Generate a new random 256-bit key
105    pub fn generate() -> Self {
106        let mut key = vec![0u8; 32];
107        OsRng.fill_bytes(&mut key);
108        Self { key }
109    }
110
111    /// Get key bytes
112    pub fn as_bytes(&self) -> &[u8] {
113        &self.key
114    }
115}
116
117/// Password hashing utilities using Argon2id
118pub mod password {
119    use super::*;
120
121    /// Hash a password using Argon2id
122    ///
123    /// Uses Argon2id with secure default parameters:
124    /// - Memory cost: 19 MiB
125    /// - Time cost: 2 iterations
126    /// - Parallelism: 1
127    pub fn hash_password(password: &SecurePassword) -> Result<String, CryptoError> {
128        let salt = SaltString::generate(&mut OsRng);
129        let argon2 = Argon2::default();
130
131        let password_hash = argon2
132            .hash_password(password.as_bytes(), &salt)
133            .map_err(|e| CryptoError::HashingError(e.to_string()))?;
134
135        Ok(password_hash.to_string())
136    }
137
138    /// Verify a password against a hash
139    pub fn verify_password(password: &SecurePassword, hash: &str) -> Result<bool, CryptoError> {
140        let parsed_hash =
141            PasswordHash::new(hash).map_err(|e| CryptoError::HashingError(e.to_string()))?;
142
143        let argon2 = Argon2::default();
144
145        match argon2.verify_password(password.as_bytes(), &parsed_hash) {
146            Ok(()) => Ok(true),
147            Err(_) => Ok(false),
148        }
149    }
150
151    /// Password strength requirements
152    #[derive(Debug, Clone)]
153    pub struct PasswordStrength {
154        pub min_length: usize,
155        pub require_uppercase: bool,
156        pub require_lowercase: bool,
157        pub require_digit: bool,
158        pub require_special: bool,
159    }
160
161    impl Default for PasswordStrength {
162        fn default() -> Self {
163            Self {
164                min_length: 12,
165                require_uppercase: true,
166                require_lowercase: true,
167                require_digit: true,
168                require_special: true,
169            }
170        }
171    }
172
173    /// Validate password strength for financial systems
174    pub fn validate_password_strength(
175        password: &SecurePassword,
176        requirements: &PasswordStrength,
177    ) -> Result<(), CryptoError> {
178        let password_str = std::str::from_utf8(password.as_bytes())
179            .map_err(|_| CryptoError::WeakPassword("Invalid UTF-8".to_string()))?;
180
181        if password_str.len() < requirements.min_length {
182            return Err(CryptoError::WeakPassword(format!(
183                "Password must be at least {} characters",
184                requirements.min_length
185            )));
186        }
187
188        if requirements.require_uppercase && !password_str.chars().any(|c| c.is_uppercase()) {
189            return Err(CryptoError::WeakPassword(
190                "Password must contain uppercase letters".to_string(),
191            ));
192        }
193
194        if requirements.require_lowercase && !password_str.chars().any(|c| c.is_lowercase()) {
195            return Err(CryptoError::WeakPassword(
196                "Password must contain lowercase letters".to_string(),
197            ));
198        }
199
200        if requirements.require_digit && !password_str.chars().any(|c| c.is_ascii_digit()) {
201            return Err(CryptoError::WeakPassword(
202                "Password must contain digits".to_string(),
203            ));
204        }
205
206        if requirements.require_special
207            && !password_str
208                .chars()
209                .any(|c| !c.is_alphanumeric() && !c.is_whitespace())
210        {
211            return Err(CryptoError::WeakPassword(
212                "Password must contain special characters".to_string(),
213            ));
214        }
215
216        Ok(())
217    }
218
219    /// Derive an encryption key from a password using Argon2
220    pub fn derive_key_from_password(
221        password: &SecurePassword,
222        salt: &[u8],
223    ) -> Result<SecureKey, CryptoError> {
224        if salt.len() < 16 {
225            return Err(CryptoError::HashingError(
226                "Salt must be at least 16 bytes".to_string(),
227            ));
228        }
229
230        // Use high-security parameters for key derivation
231        let params = Params::new(65536, 3, 1, Some(32))
232            .map_err(|e| CryptoError::HashingError(e.to_string()))?;
233
234        let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
235
236        let mut key_bytes = vec![0u8; 32];
237        argon2
238            .hash_password_into(password.as_bytes(), salt, &mut key_bytes)
239            .map_err(|e| CryptoError::HashingError(e.to_string()))?;
240
241        Ok(SecureKey { key: key_bytes })
242    }
243}
244
245/// Encryption utilities using AES-256-GCM
246pub mod encryption {
247    use super::*;
248
249    /// Encrypted data with nonce
250    pub struct EncryptedData {
251        pub ciphertext: Vec<u8>,
252        pub nonce: [u8; 12],
253    }
254
255    /// Encrypt data using AES-256-GCM
256    pub fn encrypt(key: &SecureKey, plaintext: &[u8]) -> Result<EncryptedData, CryptoError> {
257        let cipher = Aes256Gcm::new_from_slice(key.as_bytes())
258            .map_err(|e| CryptoError::EncryptionError(e.to_string()))?;
259
260        let mut nonce_bytes = [0u8; 12];
261        OsRng.fill_bytes(&mut nonce_bytes);
262        let nonce = Nonce::from_slice(&nonce_bytes);
263
264        let ciphertext = cipher
265            .encrypt(nonce, plaintext)
266            .map_err(|e| CryptoError::EncryptionError(e.to_string()))?;
267
268        Ok(EncryptedData {
269            ciphertext,
270            nonce: nonce_bytes,
271        })
272    }
273
274    /// Decrypt data using AES-256-GCM
275    pub fn decrypt(
276        key: &SecureKey,
277        encrypted: &EncryptedData,
278    ) -> Result<Vec<u8>, CryptoError> {
279        let cipher = Aes256Gcm::new_from_slice(key.as_bytes())
280            .map_err(|e| CryptoError::DecryptionError(e.to_string()))?;
281
282        let nonce = Nonce::from_slice(&encrypted.nonce);
283
284        let plaintext = cipher
285            .decrypt(nonce, encrypted.ciphertext.as_ref())
286            .map_err(|e| CryptoError::DecryptionError(e.to_string()))?;
287
288        Ok(plaintext)
289    }
290}
291
292/// Secure random number generation
293pub mod random {
294    use super::*;
295
296    /// Generate cryptographically secure random bytes
297    pub fn generate_random_bytes(length: usize) -> Vec<u8> {
298        let mut bytes = vec![0u8; length];
299        OsRng.fill_bytes(&mut bytes);
300        bytes
301    }
302
303    /// Generate a random hexadecimal string (for tokens, IDs, etc.)
304    pub fn generate_random_hex(length: usize) -> String {
305        let bytes = generate_random_bytes(length);
306        hex::encode(bytes)
307    }
308
309    /// Generate a random salt for password hashing/key derivation
310    pub fn generate_salt() -> Vec<u8> {
311        generate_random_bytes(32)
312    }
313}
314
315/// HMAC-SHA256 utilities for message authentication
316pub mod hmac_ops {
317    use super::*;
318    use hmac::Mac;
319
320    /// Compute HMAC-SHA256 for a message
321    pub fn compute_hmac(key: &SecureKey, message: &[u8]) -> Result<Vec<u8>, CryptoError> {
322        let mut mac = <HmacSha256 as Mac>::new_from_slice(key.as_bytes())
323            .map_err(|e| CryptoError::HmacError(e.to_string()))?;
324
325        mac.update(message);
326        Ok(mac.finalize().into_bytes().to_vec())
327    }
328
329    /// Verify HMAC-SHA256 for a message (constant-time comparison)
330    pub fn verify_hmac(
331        key: &SecureKey,
332        message: &[u8],
333        expected_hmac: &[u8],
334    ) -> Result<bool, CryptoError> {
335        let mut mac = <HmacSha256 as Mac>::new_from_slice(key.as_bytes())
336            .map_err(|e| CryptoError::HmacError(e.to_string()))?;
337
338        mac.update(message);
339
340        // Constant-time comparison to prevent timing attacks
341        match mac.verify_slice(expected_hmac) {
342            Ok(()) => Ok(true),
343            Err(_) => Ok(false),
344        }
345    }
346}
347
348/// Secure comparison utilities
349pub mod secure_compare {
350    /// Constant-time byte slice comparison to prevent timing attacks
351    pub fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
352        if a.len() != b.len() {
353            return false;
354        }
355
356        let mut result = 0u8;
357        for (byte_a, byte_b) in a.iter().zip(b.iter()) {
358            result |= byte_a ^ byte_b;
359        }
360
361        result == 0
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368
369    #[test]
370    fn test_password_hashing() {
371        let password = SecurePassword::new(b"MySecurePassword123!".to_vec());
372        let hash = password::hash_password(&password).unwrap();
373
374        assert!(password::verify_password(&password, &hash).unwrap());
375
376        let wrong_password = SecurePassword::new(b"WrongPassword".to_vec());
377        assert!(!password::verify_password(&wrong_password, &hash).unwrap());
378    }
379
380    #[test]
381    fn test_encryption_decryption() {
382        let key = SecureKey::generate();
383        let plaintext = b"Sensitive financial data: Account 123456, Balance: $50,000";
384
385        let encrypted = encryption::encrypt(&key, plaintext).unwrap();
386        let decrypted = encryption::decrypt(&key, &encrypted).unwrap();
387
388        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
389    }
390
391    #[test]
392    fn test_encryption_with_wrong_key() {
393        let key1 = SecureKey::generate();
394        let key2 = SecureKey::generate();
395        let plaintext = b"Secret data";
396
397        let encrypted = encryption::encrypt(&key1, plaintext).unwrap();
398        let result = encryption::decrypt(&key2, &encrypted);
399
400        assert!(result.is_err());
401    }
402
403    #[test]
404    fn test_random_generation() {
405        let bytes1 = random::generate_random_bytes(32);
406        let bytes2 = random::generate_random_bytes(32);
407
408        assert_eq!(bytes1.len(), 32);
409        assert_eq!(bytes2.len(), 32);
410        assert_ne!(bytes1, bytes2); // Should be different
411    }
412
413    #[test]
414    fn test_random_hex() {
415        let hex = random::generate_random_hex(16);
416        assert_eq!(hex.len(), 32); // 16 bytes = 32 hex characters
417    }
418
419    #[test]
420    fn test_zeroization() {
421        let password_bytes = b"TestPassword123".to_vec();
422        {
423            let _secure_password = SecurePassword::new(password_bytes.clone());
424            // Password will be zeroized when it goes out of scope
425        }
426        // In a real scenario, you'd verify memory was zeroed
427        // This test confirms the ZeroizeOnDrop trait is applied
428    }
429
430    #[test]
431    fn test_key_length_validation() {
432        let short_key = vec![0u8; 16]; // Too short
433        let result = SecureKey::new(short_key);
434        assert!(result.is_err());
435
436        let valid_key = vec![0u8; 32];
437        let result = SecureKey::new(valid_key);
438        assert!(result.is_ok());
439    }
440
441    #[test]
442    fn test_password_strength_validation() {
443        // Strong password
444        let strong = SecurePassword::new(b"SecurePass123!@#".to_vec());
445        let requirements = password::PasswordStrength::default();
446        assert!(password::validate_password_strength(&strong, &requirements).is_ok());
447
448        // Too short
449        let short = SecurePassword::new(b"Short1!".to_vec());
450        let result = password::validate_password_strength(&short, &requirements);
451        assert!(result.is_err());
452
453        // No uppercase
454        let no_upper = SecurePassword::new(b"nouppercasehere123!".to_vec());
455        let result = password::validate_password_strength(&no_upper, &requirements);
456        assert!(result.is_err());
457
458        // No special characters
459        let no_special = SecurePassword::new(b"NoSpecialChars123".to_vec());
460        let result = password::validate_password_strength(&no_special, &requirements);
461        assert!(result.is_err());
462    }
463
464    #[test]
465    fn test_key_derivation_from_password() {
466        let password = SecurePassword::new(b"MyMasterPassword123!".to_vec());
467        let salt = random::generate_salt();
468
469        let key1 = password::derive_key_from_password(&password, &salt).unwrap();
470        let key2 = password::derive_key_from_password(&password, &salt).unwrap();
471
472        // Same password and salt should produce same key
473        assert_eq!(key1.as_bytes(), key2.as_bytes());
474
475        // Different salt should produce different key
476        let different_salt = random::generate_salt();
477        let key3 = password::derive_key_from_password(&password, &different_salt).unwrap();
478        assert_ne!(key1.as_bytes(), key3.as_bytes());
479    }
480
481    #[test]
482    fn test_hmac_generation_and_verification() {
483        let key = SecureKey::generate();
484        let message = b"Important financial transaction data";
485
486        let hmac_result = hmac_ops::compute_hmac(&key, message).unwrap();
487        assert_eq!(hmac_result.len(), 32); // SHA-256 produces 32 bytes
488
489        // Verify correct HMAC
490        assert!(hmac_ops::verify_hmac(&key, message, &hmac_result).unwrap());
491
492        // Verify fails with wrong key
493        let wrong_key = SecureKey::generate();
494        assert!(!hmac_ops::verify_hmac(&wrong_key, message, &hmac_result).unwrap());
495
496        // Verify fails with modified message
497        let modified_message = b"Modified transaction data";
498        assert!(!hmac_ops::verify_hmac(&key, modified_message, &hmac_result).unwrap());
499    }
500
501    #[test]
502    fn test_constant_time_compare() {
503        let data1 = b"secret_data";
504        let data2 = b"secret_data";
505        let data3 = b"different_data";
506
507        // Same data should match
508        assert!(secure_compare::constant_time_compare(data1, data2));
509
510        // Different data should not match
511        assert!(!secure_compare::constant_time_compare(data1, data3));
512
513        // Different lengths should not match
514        let short = b"short";
515        assert!(!secure_compare::constant_time_compare(data1, short));
516    }
517
518    #[test]
519    fn test_salt_generation() {
520        let salt1 = random::generate_salt();
521        let salt2 = random::generate_salt();
522
523        assert_eq!(salt1.len(), 32);
524        assert_eq!(salt2.len(), 32);
525        assert_ne!(salt1, salt2); // Should be different
526    }
527
528    #[test]
529    fn test_password_based_encryption() {
530        // Simulate real-world password-based encryption
531        let password = SecurePassword::new(b"UserMasterPassword123!".to_vec());
532        let salt = random::generate_salt();
533
534        // Derive encryption key from password
535        let key = password::derive_key_from_password(&password, &salt).unwrap();
536
537        // Encrypt data
538        let sensitive_data = b"Social Security Number: 123-45-6789";
539        let encrypted = encryption::encrypt(&key, sensitive_data).unwrap();
540
541        // Decrypt with same password-derived key
542        let decrypted = encryption::decrypt(&key, &encrypted).unwrap();
543
544        assert_eq!(sensitive_data.as_slice(), decrypted.as_slice());
545    }
546
547    #[test]
548    fn test_multiple_hmac_computations() {
549        let key = SecureKey::generate();
550        let message1 = b"Message 1";
551        let message2 = b"Message 2";
552
553        let hmac1 = hmac_ops::compute_hmac(&key, message1).unwrap();
554        let hmac2 = hmac_ops::compute_hmac(&key, message2).unwrap();
555
556        // Different messages should produce different HMACs
557        assert_ne!(hmac1, hmac2);
558
559        // Each HMAC should verify against its own message
560        assert!(hmac_ops::verify_hmac(&key, message1, &hmac1).unwrap());
561        assert!(hmac_ops::verify_hmac(&key, message2, &hmac2).unwrap());
562
563        // HMACs should not cross-verify
564        assert!(!hmac_ops::verify_hmac(&key, message1, &hmac2).unwrap());
565        assert!(!hmac_ops::verify_hmac(&key, message2, &hmac1).unwrap());
566    }
567
568    #[test]
569    fn test_encryption_with_derived_key() {
570        let password = SecurePassword::new(b"StrongPassword789!@#".to_vec());
571        let salt = random::generate_salt();
572        let key = password::derive_key_from_password(&password, &salt).unwrap();
573
574        let data = b"Encrypted with derived key";
575        let encrypted = encryption::encrypt(&key, data).unwrap();
576
577        // Verify we can decrypt
578        let decrypted = encryption::decrypt(&key, &encrypted).unwrap();
579        assert_eq!(data.as_slice(), decrypted.as_slice());
580
581        // Verify wrong password can't decrypt
582        let wrong_password = SecurePassword::new(b"WrongPassword123!".to_vec());
583        let wrong_key = password::derive_key_from_password(&wrong_password, &salt).unwrap();
584        let result = encryption::decrypt(&wrong_key, &encrypted);
585        assert!(result.is_err());
586    }
587}