1use std::{
36 fs::{self, File},
37 path::Path,
38};
39
40use aes_gcm_siv::{
41 aead::{generic_array::GenericArray, rand_core::RngCore, Aead, OsRng},
42 Aes256GcmSiv, KeyInit, Nonce,
43};
44use anyhow::{anyhow, Context, Result};
45use argon2::Config;
46use log::{info, trace};
47use serde_derive::{Deserialize, Serialize};
48use tar::{Archive, Builder};
49
50#[derive(Serialize, Deserialize)]
51struct PrecryptorFile {
52 data: Vec<u8>,
53 nonce: [u8; 12],
54 salt: [u8; 32],
55}
56
57pub fn encrypt(data: &[u8], password: &[u8]) -> Result<Vec<u8>> {
70 trace!("Generating salt");
71 let mut salt = [0u8; 32];
72 OsRng.fill_bytes(&mut salt);
73 let config = Config {
74 hash_length: 32,
75 ..Default::default()
76 };
77
78 trace!("Generating key");
79 let password = argon2::hash_raw(password, &salt, &config)
80 .with_context(|| "Failed to generate key from password")?;
81
82 let key = GenericArray::from_slice(&password);
83 let cipher = Aes256GcmSiv::new(key);
84
85 trace!("Generating nonce");
86 let mut nonce_rand = [0u8; 12];
87 OsRng.fill_bytes(&mut nonce_rand);
88 let nonce = Nonce::from_slice(&nonce_rand);
89
90 info!("Encrypting");
91 let ciphertext = match cipher.encrypt(nonce, data.as_ref()) {
92 Ok(ciphertext) => ciphertext,
93 Err(_) => return Err(anyhow!("Failed to encrypt data -> invalid password")),
94 };
95
96 let file = PrecryptorFile {
97 data: ciphertext,
98 nonce: nonce_rand,
99 salt,
100 };
101
102 trace!("Encoding");
103 let encoded: Vec<u8> = bincode::serialize(&file).with_context(|| "Failed to decode data")?;
104
105 Ok(encoded)
106}
107
108pub fn decrypt(data: &[u8], password: &[u8]) -> Result<Vec<u8>> {
125 trace!("Decoding");
126 let decoded: PrecryptorFile =
127 bincode::deserialize(data).with_context(|| "Failed to decode data")?;
128
129 let config = Config {
130 hash_length: 32,
131 ..Default::default()
132 };
133
134 trace!("Generating key");
135 let password = argon2::hash_raw(password, &decoded.salt, &config)
136 .with_context(|| "Failed to generate key from password")?;
137
138 let key = GenericArray::from_slice(&password);
139 let cipher = Aes256GcmSiv::new(key);
140 let nonce = Nonce::from_slice(&decoded.nonce);
141
142 info!("Decrypting");
143 let text = match cipher.decrypt(nonce, decoded.data.as_ref()) {
144 Ok(ciphertext) => ciphertext,
145 Err(_) => return Err(anyhow!("Failed to encrypt data -> invalid password")),
146 };
147
148 Ok(text)
149}
150
151pub fn encrypt_file(path: &Path, output_path: &Path, password: &[u8]) -> Result<()> {
164 trace!("Reading file");
165 let data = fs::read(path).with_context(|| "Failed to read the file")?;
166 let encrypted_data = encrypt(&data, password).with_context(|| "Failed to encrypt data")?;
167 trace!("Writing to file");
168 fs::write(output_path, encrypted_data).with_context(|| "Failed to write to file")?;
169 Ok(())
170}
171
172pub fn decrypt_file(path: &Path, output_path: &Path, password: &[u8]) -> Result<()> {
185 trace!("Reading file");
186 let encrypted_data = fs::read(path).with_context(|| "Failed to read the file")?;
187 let data = decrypt(&encrypted_data, password).with_context(|| "Failed to decrypt data")?;
188 trace!("Writing to file");
189 fs::write(output_path, data).with_context(|| "Failed to write to file")?;
190 Ok(())
191}
192
193pub fn encrypt_directory(path: &Path, output_path: &Path, password: &[u8]) -> Result<()> {
208 trace!("Creating temporarily file");
209 let file = File::create(format!("{}.tmp", output_path.display()))
210 .with_context(|| "Failed to create file")?;
211 let mut archive = Builder::new(file);
212
213 trace!("Adding folder to file");
214 archive
215 .append_dir_all(
216 path.file_name().with_context(|| "Failed to get filename")?,
217 path,
218 )
219 .with_context(|| "Failed to add the folder to the file")?;
220
221 trace!("Finishing writing to file");
222 archive
223 .finish()
224 .with_context(|| "Failed to finish writing the archive")?;
225
226 trace!("Reading from temporarily file");
227 let data = fs::read(format!("{}.tmp", output_path.display()))
228 .with_context(|| "Failed to read the file")?;
229
230 trace!("Removing temporarily file");
231 fs::remove_file(format!("{}.tmp", output_path.display()))
232 .with_context(|| "Failed to remove the temporarily file")?;
233
234 let encrypted_data = encrypt(&data, password).with_context(|| "Failed to encrypt data")?;
235
236 trace!("Writing to file");
237 fs::write(output_path, encrypted_data).with_context(|| "Failed to write to file")?;
238 Ok(())
239}
240
241pub fn decrypt_directory(path: &Path, output_path: &Path, password: &[u8]) -> Result<()> {
256 trace!("Reading from file");
257 let encrypted_data = fs::read(path).with_context(|| "Failed to read the file")?;
258 let data = decrypt(&encrypted_data, password).with_context(|| "Failed to decrypt data")?;
259
260 trace!("Writing to temporarily file");
261 fs::write(format!("{}.tmp", output_path.display()), data)
262 .with_context(|| "Failed to write to file")?;
263
264 trace!("Opening file");
265 let file = File::open(format!("{}.tmp", output_path.display()))
266 .with_context(|| "Failed to open file")?;
267 let mut archive = Archive::new(file);
268
269 trace!("Extracting file");
270 archive
271 .unpack(output_path)
272 .with_context(|| "Failed to extract directory from file")?;
273
274 trace!("Removing temporarily file");
275 fs::remove_file(format!("{}.tmp", output_path.display()))
276 .with_context(|| "Failed to remove the temporarily file")?;
277 Ok(())
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn data() {
286 let encrypted_data = encrypt(b"test", b"test").expect("Failed to encrypt");
287 let data = decrypt(&encrypted_data, b"test").expect("Failed to decrypt");
288 assert_eq!(data, b"test");
289 }
290
291 #[test]
292 fn file() {
293 fs::write("test.txt", "test").expect("Failed to write to file");
294 encrypt_file(Path::new("test.txt"), Path::new("test.txt"), b"test")
295 .expect("Failed to encrypt the file");
296 decrypt_file(Path::new("test.txt"), Path::new("test.txt"), b"test")
297 .expect("Failed to decrypt the file");
298 let data = fs::read("test.txt").expect("Failed to read file");
299 assert_eq!(data, b"test");
300 fs::remove_file("test.txt").expect("Failed to remove the test file");
301 }
302
303 #[test]
304 fn directory() {
305 fs::create_dir("test").expect("Failed to create directory");
306 fs::write("test/test.txt", "test").expect("Failed to write to file");
307 encrypt_directory(Path::new("test"), Path::new("test.dir"), b"test")
308 .expect("Failed to encrypt directory");
309 fs::remove_dir_all("test").expect("Failed to remove test directory");
310 decrypt_directory(Path::new("test.dir"), Path::new("."), b"test")
311 .expect("Failed to decrypt directory");
312 fs::remove_file("test.dir").expect("Failed to remove file");
313 fs::remove_dir_all("test").expect("Failed to remove test directory");
314 }
315}