extensible_encrypter/
encrypter.rs1use 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}
28impl 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 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 let hasher = super::hasher::pbkdf2::Hasher::hash(
79 password,
80 &config.hash_rounds,
81 config.hash_algorithm,
82 Some(salt),
83 )?;
84
85 let key = hex::decode(hasher.hash().as_str()).unwrap();
87 let key_array: [u8; 32] = key.try_into().unwrap();
88
89 let cipher = Aes256GcmSiv::new_from_slice(&key_array).unwrap();
91
92 let mut nonce = [0u8; 12]; 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 let ciphertext = cipher
107 .encrypt(&nonce, plaintext.as_bytes())
108 .expect("Encryption failed"); 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>, 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 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); 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); 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}