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