street_cred/encryption/
message_encryptor.rs

1use crate::CipherGeneration;
2use crate::serialization::RubyMarshal;
3use aes_gcm::{
4  Aes128Gcm,
5  aead::{Aead, KeyInit, generic_array::GenericArray},
6};
7use anyhow::anyhow;
8use base64::{Engine as _, engine::general_purpose};
9
10/// A storage container that represents a message you want to encrypt/decrypt.
11/// In order for both operations to work, you also need to store the encryption key
12/// and additional authenticated data (plaintext).
13///
14/// # Examples
15///
16/// You can create a `MessageEncryption` using the following code:
17///
18/// ```
19/// use street_cred::MessageEncryption;
20///
21/// let message = b"secret message".to_vec();
22/// let key = "425D76994EE6101105DDDA2EE2604AA0";
23/// let aad = "additional authenticated data";
24/// let encryptor = MessageEncryption::new(message, key, aad);
25/// ```
26pub struct MessageEncryption {
27  message: Vec<u8>,
28  key: String,
29  aad: String,
30}
31
32impl MessageEncryption {
33  /// Create a new instance of MessageEncryption
34  ///
35  /// # Arguments
36  /// * `message` - Message to be encrypted
37  /// * `key` - Key to use for encryption/decryption
38  /// * `aad` - Additional authenticated data
39  ///
40  /// # Examples
41  /// ```
42  /// use street_cred::MessageEncryption;
43  ///
44  /// let message = b"secret message".to_vec();
45  /// let key = "425D76994EE6101105DDDA2EE2604AA0";
46  /// let aad = "additional authenticated data";
47  /// let encryptor = MessageEncryption::new(message, key, aad);
48  /// ```
49  pub fn new(message: Vec<u8>, key: &str, aad: &str) -> Self {
50    MessageEncryption {
51      message,
52      key: key.to_string(),
53      aad: aad.to_string(),
54    }
55  }
56
57  /// Decrypts the contents of the `MessageEncryption` and returns them as a `String`
58  ///
59  /// # Arguments
60  ///
61  /// * `iv` - Initialization vector used when initially encrypting the message
62  /// * `tag` - Additional Authenticated data resulting from encrypting the message
63  ///
64  /// # Examples
65  ///
66  /// ```
67  /// use street_cred::MessageEncryption;
68  ///
69  /// let encrypted_message = b"".to_vec();
70  /// let key = "425D76994EE6101105DDDA2EE2604AA0";
71  /// let plaintext_aad = "";
72  /// let iv = "fWoW3cyLE2/JfiiF";
73  /// let tag = "DyMEJPXzmksJGb+QumM2Rd6X";
74  ///
75  /// let decryptor = MessageEncryption::new(encrypted_message, key, plaintext_aad);
76  /// let decrypted_contents = decryptor.decrypt(iv, tag);
77  ///
78  /// match decrypted_contents {
79  ///   Ok(contents) => println!("Decrypted Contents: {}", contents),
80  ///   Err(why) => println!("Error: {}", why),
81  /// }
82  /// ```
83  pub fn decrypt(&self, iv: &str, tag: &str) -> anyhow::Result<String> {
84    if let (Ok(key), Ok(iv), Ok(message), Ok(tag)) = (
85      hex_to_bytes(&self.key),
86      general_purpose::STANDARD.decode(iv),
87      general_purpose::STANDARD.decode(&self.message),
88      general_purpose::STANDARD.decode(tag),
89    ) {
90      let key = GenericArray::from_slice(&key);
91      let iv = GenericArray::from_slice(&iv);
92      let decipher = Aes128Gcm::new(key);
93
94      let mut ciphertext = message;
95      ciphertext.extend_from_slice(&tag);
96
97      let payload = aes_gcm::aead::Payload {
98        msg: &ciphertext,
99        aad: self.aad.as_bytes(),
100      };
101
102      let plaintext = decipher.decrypt(iv, payload);
103
104      if let Ok(plaintext) = plaintext {
105        let content = RubyMarshal::deserialize(plaintext)?;
106
107        return Ok(String::from_utf8(content)?);
108      }
109    }
110
111    Err(anyhow!("Decryption not successful"))
112  }
113
114  /// Encrypts the contents of the `MessageEncryption` and returns them as a `String`
115  ///
116  /// # Examples
117  ///
118  /// ```
119  /// use street_cred::MessageEncryption;
120  ///
121  /// let plaintext_message = b"super secret message".to_vec();
122  /// let key = "16 byte key line";
123  /// let plaintext_aad = "";
124  /// let encryptor = MessageEncryption::new(plaintext_message, key, plaintext_aad);
125  /// let encrypted_contents = encryptor.encrypt();
126  ///
127  /// match encrypted_contents {
128  ///   Ok(contents) => println!("Encrypted Contents: {}", contents),
129  ///   Err(why) => println!("Error: {}", why),
130  /// }
131  /// ```
132  pub fn encrypt(&self) -> anyhow::Result<String> {
133    if let Ok(key) = hex_to_bytes(&self.key) {
134      let key = GenericArray::from_slice(&key);
135      let random_iv = CipherGeneration::random_iv();
136      let random_iv = GenericArray::from_slice(&random_iv);
137      let cipher = Aes128Gcm::new(key);
138
139      let serialized_message = RubyMarshal::serialize(std::str::from_utf8(&self.message)?)?;
140
141      let payload = aes_gcm::aead::Payload {
142        msg: &serialized_message,
143        aad: self.aad.as_bytes(),
144      };
145
146      let encrypted = cipher.encrypt(random_iv, payload);
147
148      if let Ok(encrypted) = encrypted {
149        let (ct, tag) = encrypted.split_at(encrypted.len() - 16);
150
151        let encryption_result = format!(
152          "{}--{}--{}",
153          general_purpose::STANDARD.encode(ct),
154          general_purpose::STANDARD.encode(random_iv),
155          general_purpose::STANDARD.encode(tag)
156        );
157
158        return Ok(encryption_result);
159      }
160    }
161
162    Err(anyhow!("Encryption not successful"))
163  }
164
165  /// Split contents of an encrypted file into a Vec with a length of 3.
166  /// The first index is the encrypted contents, the second index is the
167  /// initialization vector, and the third index is the additional authenticated
168  /// data.
169  ///
170  /// # Arguments
171  ///
172  /// * `contents` - The entire encrypted file as one long string. Encrypted
173  ///   contents should be formatted like this: "message--iv--aad"
174  ///
175  /// # Examples
176  ///
177  /// ```
178  /// use street_cred::MessageEncryption;
179  ///
180  /// let encrypted_contents = "HPxd1UcM3cH+Rt0HaIOFzdHqIPWIc3yR--/EoLW7ichWLzLh3V--7L1L8uPH7LoQYLkEfIckgA==";
181  /// let split_parts = MessageEncryption::split_encrypted_contents(encrypted_contents);
182  /// ```
183  pub fn split_encrypted_contents(contents: &str) -> anyhow::Result<Vec<&str>> {
184    let contents = contents.split("--").fold(Vec::new(), |mut acc, content| {
185      acc.push(content);
186
187      acc
188    });
189
190    if contents.len() == 3 {
191      Ok(contents)
192    } else {
193      Err(anyhow!("Invalid encrypted contents"))
194    }
195  }
196}
197
198fn hex_to_bytes(raw_hex: &str) -> Result<Vec<u8>, hex::FromHexError> {
199  hex::decode(raw_hex)
200}
201
202#[cfg(test)]
203mod tests {
204  use super::*;
205
206  #[test]
207  fn test_encrypt_decrypt_cycle() {
208    let key = "8872ebc11db3ea2ed08cc629d199b164";
209    let aad = "";
210    let plaintext_message = b"banana: true
211apple: false
212orange: false";
213
214    let encryptor = MessageEncryption::new(plaintext_message.to_vec(), key, aad);
215
216    let encrypted_result = match encryptor.encrypt() {
217      Ok(encrypted_contents) => encrypted_contents,
218      Err(..) => panic!("first encryption failed"),
219    };
220
221    let split_data = MessageEncryption::split_encrypted_contents(&encrypted_result).unwrap();
222
223    let new_message = split_data[0];
224    let new_iv = split_data[1];
225    let new_aad = split_data[2];
226
227    let decryptor = MessageEncryption::new(new_message.as_bytes().to_vec(), key, aad);
228
229    let decrypted_result = decryptor.decrypt(new_iv, new_aad);
230
231    let encryptor = match decrypted_result {
232      Ok(decrypted_contents) => {
233        MessageEncryption::new(decrypted_contents.as_bytes().to_vec(), key, aad)
234      }
235      Err(why) => panic!("first decryption failed {}", why),
236    };
237
238    let encrypted_result = match encryptor.encrypt() {
239      Ok(encrypted_contents) => encrypted_contents,
240      Err(why) => panic!("second encryption failed {}", why),
241    };
242
243    let split_data = MessageEncryption::split_encrypted_contents(&encrypted_result).unwrap();
244    let new_message = split_data[0];
245    let new_iv = split_data[1];
246    let new_aad = split_data[2];
247
248    let decryptor = MessageEncryption::new(new_message.as_bytes().to_vec(), key, aad);
249
250    let decrypted_result = decryptor.decrypt(new_iv, new_aad);
251
252    match decrypted_result {
253      Ok(decrypted_contents) => {
254        assert_eq!(decrypted_contents.as_bytes(), plaintext_message);
255      }
256      Err(_) => panic!("second decryption failed"),
257    };
258  }
259
260  #[test]
261  fn test_encryption_decryption_with_aad() {
262    let key = "8872ebc11db3ea2ed08cc629d199b164";
263    let aad = "some value";
264    let plaintext_message = "banana: true
265  apple: false
266  orange: false";
267
268    let encryptor = MessageEncryption::new(plaintext_message.as_bytes().to_vec(), key, aad);
269
270    let encrypted_result = match encryptor.encrypt() {
271      Ok(encrypted_contents) => encrypted_contents,
272      Err(..) => panic!("first encryption failed"),
273    };
274
275    let split_data = MessageEncryption::split_encrypted_contents(&encrypted_result).unwrap();
276
277    let new_message = split_data[0];
278    let new_iv = split_data[1];
279    let new_aad = split_data[2];
280
281    let decryptor = MessageEncryption::new(new_message.as_bytes().to_vec(), key, aad);
282
283    let result = decryptor.decrypt(new_iv, new_aad);
284
285    assert_eq!(plaintext_message, result.unwrap());
286  }
287
288  #[test]
289  fn test_decryption_fails_with_incorrect_iv() {
290    let key = "94b6b40cabf62ee59c9aa13a86f0e7d7";
291    let aad = "";
292    let encrypted_message = b"1alR88JGbSy1wz44cgVgZC3mH2Fg9HjRFtl6NwRoOfpqNzJ61Ub48O1YhJUqaszJgJ8=";
293    let decryptor = MessageEncryption::new(encrypted_message.to_vec(), key, aad);
294
295    let result = decryptor.decrypt("123456789012345", "pksKcg/so9Pq3UMHjfnVsg==");
296
297    assert!(result.is_err());
298  }
299
300  #[test]
301  fn test_encryption_fails_with_non_hex_key() {
302    let key = "8872ebc11db3ea2";
303    let aad = "";
304    let plaintext_message = b"banana: true
305apple: false
306orange: false";
307
308    let encryptor = MessageEncryption::new(plaintext_message.to_vec(), key, aad);
309
310    let result = encryptor.encrypt();
311
312    assert!(result.is_err());
313  }
314
315  #[test]
316  fn test_invalid_aad_for_decrypt() {
317    let invalid_aad = "66ag";
318    let aad = "";
319    let key = "8872ebc11db3ea2";
320    let plaintext_message = b"banana: true
321apple: false
322orange: false";
323
324    let decryptor = MessageEncryption::new(plaintext_message.to_vec(), key, aad);
325
326    let result = decryptor.decrypt("", invalid_aad);
327
328    assert!(result.is_err());
329  }
330}