password_encryptor/
lib.rs

1mod error;
2
3use error::{Error, Result};
4use hmac::{Hmac, Mac};
5use sha2::Sha512;
6
7
8#[derive(Clone)]
9pub struct EncryptionData<'a> {
10    pub content: &'a str,
11    pub salt: &'a str,
12}
13
14#[derive(Clone)]
15pub struct PasswordEncryptor {
16    key: Vec<u8>,
17    encryption_prefix: Option<String>,
18}
19
20impl PasswordEncryptor {
21    pub fn new(key: Vec<u8>, encryption_prefix: Option<String>) -> Self {
22        Self {
23            key,
24            encryption_prefix,
25        }
26    }
27}
28
29impl PasswordEncryptor {
30    fn encrypt_into_base64url(
31        &self,
32        key: &[u8],
33        encryption_data: &EncryptionData,
34    ) -> Result<String> {
35        let EncryptionData { content, salt } = encryption_data;
36
37        let mut hmac_sha512 =
38            Hmac::<Sha512>::new_from_slice(key).map_err(|_| Error::KeyFailHmac)?;
39
40        hmac_sha512.update(content.as_bytes());
41        hmac_sha512.update(salt.as_bytes());
42
43        let hmac_result = hmac_sha512.finalize();
44        let result_bytes = hmac_result.into_bytes();
45
46        let result = base64_url::encode(&result_bytes);
47
48        Ok(result)
49    }
50
51    pub fn encrypt_password(&self, encryption_data: &EncryptionData) -> Result<String> {
52        let encrypted = self.encrypt_into_base64url(&self.key, encryption_data)?;
53        let final_prefix = self.encryption_prefix.clone().unwrap_or("".to_string());
54        Ok(format!("{final_prefix}{encrypted}"))
55    }
56
57    pub fn validate_password(
58        &self,
59        encryption_data: &EncryptionData,
60        encrypted_password: &str,
61    ) -> Result<()> {
62        let inner_encrypted_password = self.encrypt_password(encryption_data)?;
63        if inner_encrypted_password == encrypted_password {
64            Ok(())
65        } else {
66            Err(Error::PasswordsDontMatch)
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_successful_encryption() {
77        let encryptor = PasswordEncryptor::new(vec![1,2,3], None);
78        let data = EncryptionData {
79            content: "password123",
80            salt: "salt",
81        };
82
83        let encrypted = encryptor.encrypt_password(&data).unwrap();
84        assert!(!encrypted.is_empty())
85    }
86
87    #[test]
88    fn test_validation_success() {
89        let encryptor = PasswordEncryptor::new(vec![1,2,3], None);
90        let data = EncryptionData {
91            content: "password123",
92            salt: "salt",
93        };
94
95        let encrypted = encryptor.encrypt_password(&data).unwrap();
96        assert!(encryptor.validate_password(&data, &encrypted).is_ok());
97    }
98
99    #[test]
100    fn test_validation_failure() {
101        let encryptor = PasswordEncryptor::new(vec![1,2,3], Some("prefix_".to_string()));
102        let data = EncryptionData {
103            content: "password123",
104            salt: "salt",
105        };
106
107        assert!(encryptor
108            .validate_password(&data, "wrong_password")
109            .is_err());
110    }
111
112    #[test]
113    fn test_password_mismatch_error() {
114        let encryptor = PasswordEncryptor::new(vec![1,2,3], Some("prefix_".to_string()));
115        let data = EncryptionData {
116            content: "password123",
117            salt: "salt",
118        };
119
120        let wrong_data = EncryptionData {
121            content: "wrong_password",
122            salt: "salt",
123        };
124
125        let encrypted = encryptor.encrypt_password(&data).unwrap();
126        match encryptor.validate_password(&wrong_data, &encrypted) {
127            Err(Error::PasswordsDontMatch) => (),
128            _ => panic!("Expected PasswordsDontMatch error"),
129        }
130    }
131}