simple_crypt/
lib.rs

1//! # Simple Crypt
2//!
3//! `simple_crypt` is a high-level library to encrypt and decrypt data
4//!
5//! For encryption it uses [AES-GCM-SIV-256](https://en.wikipedia.org/wiki/AES-GCM-SIV) and [Argon2](https://en.wikipedia.org/wiki/Argon2)
6//!
7//! ## Usage
8//!
9//! add this to Cargo.toml:
10//!
11//! ```toml
12//! simple_crypt = "*"
13//! ```
14//!
15//! ## Examples
16//!
17//! ```no_run
18//! // Encrypting
19//!
20//! use simple_crypt::encrypt;
21//! let encrypted_data = encrypt(b"example text", b"example password").expect("Failed to encrypt");
22//!
23//! // Decrypting
24//!
25//! use simple_crypt::decrypt;
26//! let data = decrypt(&encrypted_data, b"example password").expect("Failed to decrypt");
27//! ```
28//!
29//! And there are other functions to encrypt files or folders see the [documentation](https://docs.rs/simple_crypt)
30//!
31//! [Documentation](https://docs.rs/simple_crypt)
32//! [Repository](https://github.com/NiiightmareXD/simple_crypt)
33//!
34
35use 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
57/// Encrypts some data and returns the result
58///
59/// # Examples
60///
61/// ```no_run
62/// use simple_crypt::encrypt;
63///
64/// let encrypted_data = encrypt(b"example text", b"example password").expect("Failed to encrypt");
65/// // and now you can write it to a file:
66/// // fs::write("encrypted_text.txt", encrypted_data).expect("Failed to write to file");
67/// ```
68///
69pub 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
108/// Decrypts some data and returns the result
109///
110/// # Examples
111///
112/// ```no_run
113/// use simple_crypt::{encrypt, decrypt};
114///
115/// let encrypted_data = encrypt(b"example text", b"example password").expect("Failed to encrypt");
116///
117/// let data = decrypt(&encrypted_data, b"example passowrd").expect("Failed to decrypt");
118/// // and now you can print it to stdout:
119/// // println!("data: {}", String::from_utf8(data.clone()).expect("Data is not a utf8 string"));
120/// // or you can write it to a file:
121/// // fs::write("text.txt", data).expect("Failed to write to file");
122/// ```
123///
124pub 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
151/// Encrypts file data and outputs it to the specified output file
152///
153/// # Examples
154///
155/// ```no_run
156/// use simple_crypt::encrypt_file;
157/// use std::path::Path;
158///
159/// encrypt_file(Path::new("example.txt"), Path::new("encrypted_example.txt"), b"example passwprd").expect("Failed to encrypt the file");
160/// // Now the encrypted_example.txt is encrypted
161/// ```
162///
163pub 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
172/// Decrypts file data and output it to the specified output file
173///
174/// # Examples
175///
176/// ```no_run
177/// use simple_crypt::decrypt_file;
178/// use std::path::Path;
179///
180/// decrypt_file(Path::new("encrypted_example.txt"), Path::new("example.txt"), b"example passwprd").expect("Failed to decrypt the file");
181/// // Now the example.txt is decrypted
182/// ```
183///
184pub 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
193/// Encrypts a directory and outputs it to the specified output file
194///
195/// note: the output is a file but when you decrypt it, it will be a directory again it's simply an encrypted tar file
196///
197/// # Examples
198///
199/// ```no_run
200/// use simple_crypt::encrypt_directory;
201/// use std::path::Path;
202///
203/// encrypt_directory(Path::new("example"), Path::new("example.dir"), b"example password").expect("Failed to encrypt directory");
204/// // Now the example.dir is encrypted
205/// ```
206///
207pub 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
241/// Decrypts a directory and extracts it to the specified output directory
242///
243/// note: the encrypted directory is a file but when its decrypted it will be a directory and the output path is not what the folder name should be its where to extract the file
244///
245/// # Examples
246///
247/// ```no_run
248/// use simple_crypt::decrypt_directory;
249/// use std::path::Path;
250///
251/// decrypt_directory(Path::new("example.dir"), Path::new("example"), b"example password").expect("Failed to decrypt directory");
252/// // Now the example.txt is decrypted
253/// ```
254///
255pub 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}