cargo_cryptile/
lib.rs

1//! Cryptile:
2//! CLI tool for Encrypting and Decrypting files
3//! with a password using AES256 encryption
4//! 
5//! # Example
6//! ```
7//! use cargo_cryptile as cryptile;
8//! use hmac_sha256::Hash;
9//! 
10//! let pass = "password";
11//! let pass: Vec<u8> = (*pass).bytes().collect();
12//! let key = Hash::hash(&pass);
13//! 
14//! cryptile::encrypt("test.txt", &key).unwrap();
15//! cryptile::decrypt("test.txt.cryptile", &key).unwrap();
16//! ```
17
18
19use std::io::{Read, Write};
20use std::fs::{self, File};
21use std::io::{Error, ErrorKind};
22use aes::Aes256;
23use aes::cipher::{
24    BlockEncrypt, BlockDecrypt, KeyInit,
25    generic_array::GenericArray,
26};
27use hmac_sha256::Hash;
28
29enum Stage {
30    Encrypt,
31    Decrypt(Option<u8>)
32}
33
34const SMALL_FILE_SIZE_LIMIT: u64 = 26_214_400 * 2;
35const CHUNK_SIZE: usize = 26_214_400;
36pub const FILE_EXTENSION: &str = ".cryptile";
37
38
39fn cipher_init(key: &[u8; 32]) -> Aes256 {
40    let key = GenericArray::from(*key);
41    Aes256::new(&key)
42}
43
44fn encrypt_chunk_serially(blocks: &mut Vec<[u8; 16]>, cipher: &Aes256) {
45    for byte_block in blocks.iter_mut() {
46        let mut block = GenericArray::from(byte_block.to_owned());
47
48        cipher.encrypt_block(&mut block);
49        
50        for (i, byte) in block.bytes().enumerate() {
51            if let Ok(byte) = byte {
52                (*byte_block)[i] = byte;
53            }
54        }
55    }
56}
57
58fn decrypt_chunk_serially(blocks: &mut Vec<[u8; 16]>, cipher: &Aes256) {
59    for byte_block in blocks.iter_mut() {
60        let mut block = GenericArray::from(byte_block.to_owned());
61
62        cipher.decrypt_block(&mut block);
63        
64        for (i, byte) in block.bytes().enumerate() {
65            if let Ok(byte) = byte {
66                (*byte_block)[i] = byte;
67            }
68        }
69    }
70}
71
72// use threads_pool::ThreadPool;
73
74// fn encrypt_chunk_parallelly(blocks: &mut Vec<[u8; 16]>, cipher: &Aes256, n_threads: usize) {
75//     let pool = ThreadPool::new(n_threads);
76
77//     for byte_block in blocks.iter_mut() {
78//         let block_ptr = byte_block as *mut [u8; 16];
79//         let block_ptr = block_ptr as usize;
80
81//         let cipher_ptr = cipher as *const Aes256;
82//         let cipher_ptr = cipher_ptr as usize;
83//         pool.execute(move || {
84//             let block_ptr = block_ptr as *mut [u8; 16];
85//             let cipher = cipher_ptr as *const Aes256;
86//             unsafe {
87//                 let mut block = GenericArray::from(*(block_ptr).to_owned());
88
89//                 (*cipher).encrypt_block(&mut block);
90                
91//                 for (i, byte) in block.bytes().enumerate() {
92//                     if let Ok(byte) = byte {
93//                         (*block_ptr)[i] = byte;
94//                     }
95//                 }
96//             }
97//         }).unwrap();
98//     }
99// }
100
101// fn decrypt_chunk_parallelly(blocks: &mut Vec<[u8; 16]>, cipher: &Aes256, n_threads: usize) {
102//     let pool = ThreadPool::new(n_threads);
103
104//     for byte_block in blocks.iter_mut() {
105//         let block_ptr = byte_block as *mut [u8; 16];
106//         let block_ptr = block_ptr as usize;
107
108//         let cipher_ptr = cipher as *const Aes256;
109//         let cipher_ptr = cipher_ptr as usize;
110//         pool.execute(move || {
111//             let block_ptr = block_ptr as *mut [u8; 16];
112//             let cipher = cipher_ptr as *const Aes256;
113//             unsafe {
114//                 let mut block = GenericArray::from(*(block_ptr).to_owned());
115
116//                 (*cipher).decrypt_block(&mut block);
117                
118//                 for (i, byte) in block.bytes().enumerate() {
119//                     if let Ok(byte) = byte {
120//                         (*block_ptr)[i] = byte;
121//                     }
122//                 }
123//             }
124//         }).unwrap();
125//     }
126// }
127
128fn encrypt_small_file(r_file: &mut File, cipher: &Aes256, w_file: &mut File) -> Result<(), Error> {
129    let mut blocks = read_entire_as_blocks(r_file, Stage::Encrypt)?;
130    encrypt_chunk_serially(&mut blocks, &cipher);
131    write_entire_from_blocks(w_file, &blocks, Stage::Encrypt)?;
132
133    Ok(())
134}
135
136fn encrypt_large_file(r_file: &mut File, cipher: &Aes256, w_file: &mut File) -> Result<(), Error> {
137    println!("Encrypting large file in parts...");
138    loop {
139        let (mut blocks, cont) = read_chunk_as_blocks(r_file, Stage::Encrypt)?;
140        encrypt_chunk_serially(&mut blocks, &cipher);
141        write_entire_from_blocks(w_file, &blocks, Stage::Encrypt)?;
142
143        if !cont {
144            break
145        }
146    }
147        
148    Ok(())
149}
150
151fn decrypt_small_file(r_file: &mut File, cipher: &Aes256, w_file: &mut File) -> Result<(), Error> {
152    let mut blocks = read_entire_as_blocks(r_file, Stage::Decrypt(None))?;
153    decrypt_chunk_serially(&mut blocks, &cipher);
154    let last_block = blocks.last();
155    if let Some(block) = last_block {
156        if let Some(padding) = block.last() {
157            write_entire_from_blocks(w_file, &blocks, Stage::Decrypt(Some(*padding)))?;
158        }
159        else {
160            return Err(Error::from(ErrorKind::UnexpectedEof))
161        }
162    }
163    else {
164        return Err(Error::from(ErrorKind::UnexpectedEof))
165    }
166    Ok(())
167}
168
169fn decrypt_large_file(r_file: &mut File, cipher: &Aes256, w_file: &mut File) -> Result<(), Error> {
170    println!("Decrypting large file in parts...");
171    loop {
172        let (mut blocks, cont) = read_chunk_as_blocks(r_file, Stage::Decrypt(None))?;
173        decrypt_chunk_serially(&mut blocks, &cipher);
174
175        if !cont {
176            let last_block = blocks.last();
177            if let Some(block) = last_block {
178                if let Some(padding) = block.last() {
179                    write_entire_from_blocks(w_file, &blocks, Stage::Decrypt(Some(*padding)))?;
180                }
181                else {
182                    return Err(Error::from(ErrorKind::UnexpectedEof))
183                }
184            }
185            else {
186                return Err(Error::from(ErrorKind::UnexpectedEof))
187            }
188            break
189        }
190        write_entire_from_blocks(w_file, &blocks, Stage::Decrypt(None))?;
191    }
192    Ok(())
193}
194
195fn hash_encrypt_write(hash: [u8; 32], cipher: &Aes256, file: &mut File) -> Result<(), Error> {
196    let mut temp_block = [0u8; 16];
197    for (i, byte) in hash[0..16].iter().enumerate() {
198        temp_block[i] = *byte;
199    }
200    let mut block1 = GenericArray::from(temp_block);
201
202    let mut temp_block = [0u8; 16];
203    for (i, byte) in hash[16..32].iter().enumerate() {
204        temp_block[i] = *byte;
205    }
206    let mut block2 = GenericArray::from(temp_block);
207
208    cipher.encrypt_block(&mut block1);
209    cipher.encrypt_block(&mut block2);
210
211    file.write(&block1)?;
212    file.write(&block2)?;
213    Ok(())
214}
215
216fn hash_read_decrypt(cipher: &Aes256, file: &mut File) -> Result<[u8; 32], Error> {
217    let mut block1 = [0u8; 16];
218    let mut block2 = [0u8; 16];
219    file.read(&mut block1)?;
220    file.read(&mut block2)?;
221
222    let mut block1 = GenericArray::from(block1);
223    let mut block2 = GenericArray::from(block2);
224
225    cipher.decrypt_block(&mut block1);
226    cipher.decrypt_block(&mut block2);
227
228    let mut res = [0u8; 32];
229    let mut i: usize = 0;
230    for byte in block1 {
231        res[i] = byte;
232        i += 1;
233    }
234    for byte in block2 {
235        res[i] = byte;
236        i += 1;
237    }
238
239    Ok(res)
240}
241
242fn read_entire_as_blocks(file: &mut File, stage: Stage) -> Result<Vec<[u8; 16]>, Error> {
243    let mut bytes = Vec::new();
244
245    loop {
246        let mut block = [0u8; 16];
247        match file.read(&mut block) {
248            Ok(b) if b < 16 => {
249                if let Stage::Encrypt = stage {
250                    let padding = (16 - b) as u8;
251                    block[16 - 1] = padding;
252                    bytes.push(block);
253                }
254                break
255            },
256            Err(e) => return Err(Error::from(e)),
257            Ok(_) => ()
258        }
259        bytes.push(block);
260    }
261    
262    Ok(bytes)
263}
264
265fn read_chunk_as_blocks(file: &mut File, stage: Stage) -> Result<(Vec<[u8; 16]>, bool), Error> {
266    let mut bytes = Vec::new();
267    let n_blocks = CHUNK_SIZE / 16;
268    let mut cont = true;
269
270    loop {
271        let mut block = [0u8; 16];
272        match file.read(&mut block) {
273            Ok(b) if b < 16 => {
274                if let Stage::Encrypt = stage {
275                    let padding = (16 - b) as u8;
276                    block[16 - 1] = padding;
277                    bytes.push(block);
278                }
279                cont = false;
280                break
281            },
282            Err(e) => return Err(Error::from(e)),
283            Ok(_) => ()
284        }
285        bytes.push(block);
286        if bytes.len() >= n_blocks {
287            break
288        }
289    }
290    
291    Ok((bytes, cont))
292}
293
294fn write_entire_from_blocks(file: &mut File, blocks: &Vec<[u8; 16]>, stage: Stage) -> Result<(), Error> {
295
296    let mut blocks = blocks.iter().peekable();
297    while let Some(block) = blocks.next() {
298        if blocks.peek().is_none() {
299            if let Stage::Decrypt(Some(padding)) = stage {
300                if padding > 16 {
301                    return Err(Error::from(ErrorKind::InvalidInput));
302                }
303                let t = (16 - padding) as usize;
304                if let Err(e) = file.write(&block[..t]) {
305                    return Err(e)
306                }
307                break
308            }
309        }
310        if let Err(e) = file.write(block) {
311            return Err(e)
312        }
313    }
314
315    Ok(())
316}
317
318/// Function to encrypt a file using a 32-bit key
319/// Returns Result type 
320/// 
321/// # Errors
322/// This function will return an appropriate variant of
323/// `std::io::Error` if there is any error reading the file
324/// or creating the encrypted file
325pub fn encrypt(filename: &str, key: &[u8; 32]) -> Result<(), Error> {
326    let key_hash = Hash::hash(key);
327    let cipher = cipher_init(key);
328    let new_file_name = filename.to_owned() + FILE_EXTENSION;
329
330    let mut reader = File::open(filename)?;
331    let size = reader.metadata()?.len();
332    let mut writer = File::create(&new_file_name)?;
333
334    hash_encrypt_write(key_hash, &cipher, &mut writer)?;
335
336    if size < SMALL_FILE_SIZE_LIMIT {
337        encrypt_small_file(&mut reader, &cipher, &mut writer)?;
338    }
339    else {
340        encrypt_large_file(&mut reader, &cipher, &mut writer)?;
341    }
342
343    Ok(())
344}
345
346/// Function to decrypt a previously ecrypted file using the `encrypt` function
347/// Returns Result type
348/// 
349/// # Errors
350/// This function will return an appropriate variant of
351/// `std::io::Error` if there is any error reading the file
352/// or creating the decrypted file.
353/// 
354/// If the file given as the arguement isn't a file encrypted
355/// with this tool (i.e. not ending with .cryptile),
356/// It will give a `std::io::ErrorKind::Unsupported` error.
357/// 
358/// If the key given as the arguement isn't the key used to
359/// encrypt the file and can't be used as a decryption key,
360/// It will give a `std::io::ErrorKind::InvalidInput` error.
361pub fn decrypt(filename: &str, key: &[u8; 32]) -> Result<(), Error> {
362    if !filename.ends_with(FILE_EXTENSION) {
363        return Err(Error::from(ErrorKind::Unsupported))
364    }
365
366    let key_hash = Hash::hash(key);
367    let cipher = cipher_init(key);
368
369    let new_file_name = filename.replace(FILE_EXTENSION, "");
370
371    let mut reader = File::open(filename)?;
372    let size = reader.metadata()?.len();
373
374    let hash = hash_read_decrypt(&cipher, &mut reader)?;
375    if key_hash != hash {
376        return Err(Error::from(ErrorKind::InvalidInput))
377    }
378    let mut writer = File::create(&new_file_name)?;
379
380    if size < SMALL_FILE_SIZE_LIMIT {
381        decrypt_small_file(&mut reader, &cipher, &mut writer)?;
382    }
383    else {
384        decrypt_large_file(&mut reader, &cipher, &mut writer)?;
385    }
386
387    Ok(())
388}
389
390// pub fn encrypt_parallel(filename: &str, key: &str) -> Result<(), Error> {
391//     Ok(())
392// }
393
394// pub fn decrypt_parallel(filename: &str, key: &str) -> Result<(), Error> {
395//     Ok(())
396// }
397
398// pub fn encrypt_parallel_with(filename: &str, key: &str, n_threads: usize) -> Result<(), Error> {
399//     Ok(())
400// }
401
402// pub fn decrypt_parallel_with(filename: &str, key: &str, n_threads: usize) -> Result<(), Error> {
403//     Ok(())
404// }
405
406
407/// Function to determine whether a key is correct for an encrypted file
408/// Returns a `Result<bool>` type
409/// 
410/// # Errors
411/// This function will return an appropriate variant of
412/// `std::io::Error` if there is any error reading the file
413/// 
414/// If the file given as the arguament isn't a file encrypted
415/// with this tool (i.e. not ending with .cryptile),
416/// It will give a `std::io::ErrorKind::Unsupported` error.
417pub fn is_correct_key(filename: &str, key: &[u8; 32]) -> Result<bool, Error> {
418    if !filename.ends_with(FILE_EXTENSION) {
419        return Err(Error::from(ErrorKind::Unsupported))
420    }
421
422    let key_hash = Hash::hash(key);
423    let cipher = cipher_init(key);
424    let mut reader = File::open(filename)?;
425
426    let hash = hash_read_decrypt(&cipher, &mut reader)?;
427    if key_hash != hash {
428        return Ok(false)
429    }
430
431    Ok(true)
432}
433
434/// Function to try to delete a file from filesystem
435/// to be called after encryption or decryption to delete the original file
436/// Ignores whether the delete operation fails or not
437pub fn delete(filename: &str) {
438    _ = fs::remove_file(filename);
439}
440
441
442
443pub mod benches {
444    use super::*;
445
446    pub fn bench_serially_encrypt(filename: &str) {
447        let pass = "0123456789ABCDEF";
448        let pass: Vec<u8> = (*pass).bytes().collect();
449        let key = Hash::hash(&pass);
450        encrypt(filename, &key).expect("Error in Encrypting");
451    }
452
453    // pub fn bench_parallelly_encrypt(filename: &str) {}
454
455    pub fn bench_serially_decrypt(filename: &str) {
456        let pass = "0123456789ABCDEF";
457        let pass: Vec<u8> = (*pass).bytes().collect();
458        let key = Hash::hash(&pass);
459        decrypt(filename, &key).expect("Error in Decrypting");
460    }
461
462    // pub fn bench_parallelly_decrypt(filename: &str) {}
463
464}
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469    use std::time::Duration;
470    use std::thread;
471    use std::thread::available_parallelism;
472    use threads_pool::ThreadPool;
473    const TEST_KEY: &str = "0123456789ABCDEF0123456789ABCDEF";
474
475    #[test]
476    fn encrypt_file() {
477        let pass = "0123456789ABCDEF";
478        let pass: Vec<u8> = (*pass).bytes().collect();
479        let key = Hash::hash(&pass);
480        encrypt("test.jpg", &key).expect("Error in Encrypting");
481    }
482
483    #[test]
484    fn decrypt_file() {
485        let pass = "0123456789ABCDEF";
486        let pass: Vec<u8> = (*pass).bytes().collect();
487        let key = Hash::hash(&pass);
488        decrypt("test.jpg.cryptile", &key).expect("Error in Decrypting");
489    }
490
491    #[test]
492    fn num_cpus() {
493        let default_parallelism_approx = available_parallelism().unwrap().get();
494
495        println!("Number of availaible parallelism: {}", default_parallelism_approx);
496    }
497
498    #[test]
499    fn thread_pool_test() {
500        let pool = ThreadPool::new(4);
501        
502        for i in 1..=10 {
503            println!("Starting thread {}", i);
504            pool.execute(move || {
505                thread::sleep(Duration::from_secs(1));
506                println!("thread {} finished", i);
507            }).unwrap();
508        }
509        
510        drop(pool);
511
512        println!("finished function... All tasks should be completed before this");
513    }
514}