extensible_encrypter/
encrypter.rs

1use crate::error::{self, DefaultError};
2use aes_gcm_siv::aead::rand_core::RngCore;
3use aes_gcm_siv::{
4    aead::{Aead, KeyInit},
5    Aes256GcmSiv, Nonce,
6};
7use base64::{engine::general_purpose, Engine as _};
8use pbkdf2::password_hash::SaltString;
9use rand_chacha::rand_core::SeedableRng;
10use rand_chacha::ChaCha12Rng;
11use std::io::Write;
12
13pub struct Aes256GcmSivConfig {
14    hash_rounds: u32,
15    hash_algorithm: super::hasher::pbkdf2::Algorithm,
16}
17
18#[allow(dead_code)]
19impl Aes256GcmSivConfig {
20    pub fn set_hash_algorithm(&mut self, hash_algorithm: super::hasher::pbkdf2::Algorithm) {
21        self.hash_algorithm = hash_algorithm;
22    }
23
24    pub fn set_hash_rounds(&mut self, hash_rounds: u32) {
25        self.hash_rounds = hash_rounds;
26    }
27}
28/// Default configuration for Aes256GcmSiv
29impl Default for Aes256GcmSivConfig {
30    fn default() -> Self {
31        Self {
32            hash_rounds: 600_000,
33            hash_algorithm: super::hasher::pbkdf2::Algorithm::Pbkdf2Sha512,
34        }
35    }
36}
37
38pub enum Cipher {
39    Aes256GcmSiv(Aes256GcmSivConfig),
40}
41
42pub trait EncryptProvider {
43    type Cipher;
44
45    fn encrypt(
46        &self,
47        plaintext: &str,
48        password: &str,
49        ek: Self::Cipher,
50    ) -> Result<EncryptionResult, DefaultError>;
51}
52
53pub struct Aes256GcmSivEncryptProvide;
54
55impl EncryptProvider for Aes256GcmSivEncryptProvide {
56    type Cipher = Cipher;
57
58    fn encrypt(
59        &self,
60        plaintext: &str,
61        password: &str,
62        encryption_kind: Self::Cipher,
63    ) -> Result<EncryptionResult, DefaultError> {
64        match encryption_kind {
65            Cipher::Aes256GcmSiv(config) => {
66                tracing::info!("Encrypting: Aes256GcmSiv");
67
68                // A salt for PBKDF2 (should be unique per encryption)
69                let mut salt_result = Vec::new();
70                let mut rng = ChaCha12Rng::from_os_rng();
71                let salt = SaltString::from_rng(&mut rng);
72                let salt_str = salt.as_str().as_bytes();
73                salt_result
74                    .write_all(salt_str)
75                    .expect("Failed copying salt into buffer");
76
77                // Derive a 32-byte key using PBKDF2 with SHA-512
78                let hasher = super::hasher::pbkdf2::Hasher::hash(
79                    password,
80                    &config.hash_rounds,
81                    config.hash_algorithm,
82                    Some(salt),
83                )?;
84
85                // Convert the key to a fixed-size array
86                let key = hex::decode(hasher.hash().as_str()).unwrap();
87                let key_array: [u8; 32] = key.try_into().unwrap();
88
89                // Initialize the AES-GCM-SIV cipher
90                let cipher = Aes256GcmSiv::new_from_slice(&key_array).unwrap();
91
92                // Generate a random nonce (96 bits)
93                let mut nonce = [0u8; 12]; // 96 bits = 12 bytes
94                let mut rng = ChaCha12Rng::from_os_rng();
95                rng.fill_bytes(&mut nonce);
96                let mut nonce_result = Vec::new();
97                nonce_result
98                    .write_all(&nonce)
99                    .expect("Failed copying nonce into buffer");
100
101                let nonce = Nonce::try_from(&nonce[..]).map_err(|_err| {
102                    DefaultError::ErrorMessage("Failed parsing nonce from slice".to_string())
103                })?;
104
105                // Encrypt the message
106                let ciphertext = cipher
107                    .encrypt(&nonce, plaintext.as_bytes())
108                    .expect("Encryption failed"); // Output the results
109                tracing::debug!("Nonce: {:?}", nonce);
110                tracing::debug!("Ciphertext: {:?}", ciphertext);
111
112                Ok(EncryptionResult {
113                    ciphertext,
114                    nonce: nonce_result,
115                    salt: salt_result,
116                })
117            }
118        }
119    }
120}
121
122#[derive(Debug)]
123pub struct EncryptionResult {
124    pub ciphertext: Vec<u8>,
125    pub nonce: Vec<u8>, // iv?
126    pub salt: Vec<u8>,
127}
128
129impl EncryptionResult {
130    pub fn ciphertext(&self) -> &[u8] {
131        &self.ciphertext
132    }
133
134    pub fn nonce(&self) -> &[u8] {
135        &self.nonce
136    }
137
138    pub fn salt(&self) -> &[u8] {
139        &self.salt
140    }
141
142    pub fn ciphertext_b64(&self) -> String {
143        general_purpose::STANDARD.encode(self.ciphertext())
144    }
145
146    pub fn nonce_b64(&self) -> String {
147        general_purpose::STANDARD.encode(self.nonce())
148    }
149
150    pub fn salt_b64(&self) -> String {
151        general_purpose::STANDARD.encode(self.salt())
152    }
153}
154
155pub struct Encrypter;
156
157impl Encrypter {
158    ///  Uses impl trait to accept any type that implements EncryptProvider to perform the encryption
159    pub fn encrypt<C>(
160        plaintext: &str,
161        password: &str,
162        provider: impl EncryptProvider<Cipher = C>,
163        cipher: C,
164    ) -> error::Result<EncryptionResult> {
165        provider.encrypt(plaintext, password, cipher)
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use tracing_test::traced_test;
173
174    #[traced_test]
175    #[test]
176    fn aes256_gcm_siv_e2e() {
177        let provider = Aes256GcmSivEncryptProvide;
178
179        let plaintext = "secret nuke codes go inside the football";
180        let mut cipher_config = Aes256GcmSivConfig::default();
181        cipher_config.set_hash_rounds(20); // low number of rounds for testing
182
183        let result = Encrypter::encrypt(
184            plaintext,
185            "password",
186            provider,
187            Cipher::Aes256GcmSiv(cipher_config),
188        );
189        let result = result.expect("Encryption failed");
190        tracing::info!("Result: {:?}", result);
191
192        let input = &mut crate::decrypter::builder::DecrypterBuilder::new()
193            .salt(result.salt)
194            .nonce(result.nonce)
195            .ciphertext(result.ciphertext)
196            .build();
197
198        let provider = crate::decrypter::PBKDF2DecryptProvide;
199        let mut cipher_config = crate::decrypter::Aes256GcmSivConfig::default();
200        cipher_config.set_hash_rounds(20); // low number of rounds for testing
201
202        let result = crate::decrypter::Decrypter::decrypt(
203            input,
204            provider,
205            crate::decrypter::DecrypterCipher::Aes256GcmSiv(cipher_config),
206        );
207        let result = result.expect("Decryption failed");
208
209        assert_eq!(
210            result.plaintext(),
211            "secret nuke codes go inside the football"
212        );
213    }
214}