1extern crate core;
2
3use std::error::Error;
4use std::fs::File;
5use std::io::{Read, Write};
6
7use chacha20::XChaCha20;
8use chacha20poly1305::aead::stream;
9use chacha20poly1305::aead::stream::{Decryptor, Encryptor, StreamBE32};
10use chacha20poly1305::consts::U24;
11use chacha20poly1305::{ChaChaPoly1305, KeyInit, XChaCha20Poly1305};
12
13use err::EncryptionError;
14
15use crate::err::DecryptionError;
16
17mod err;
18mod keygen;
19
20type Cypher = ChaChaPoly1305<XChaCha20, U24>;
21
22const VERSION: u8 = 1;
23const BUFFER_SIZE: usize = 1024;
24const AUTHENTICATION_TAG_SIZE: usize = 16;
25
26pub fn encrypt_file(
27 source_path: &str,
28 target_path: &str,
29 password: &str,
30) -> Result<(), Box<dyn Error>> {
31 let mut generated_key = keygen::generate_key(password)?;
32 let nonce = generated_key.nonce.as_slice();
33 let salt = generated_key.salt.as_slice();
34
35 let aead = XChaCha20Poly1305::new(generated_key.key[..32].as_ref().into());
36 let encryptor: Encryptor<Cypher, StreamBE32<Cypher>> =
37 stream::EncryptorBE32::from_aead(aead, nonce.into());
38
39 let mut source_file = File::open(source_path)?;
40 let mut destination_file = File::create(target_path)?;
41
42 write_header(&mut destination_file, salt, nonce)?;
43
44 write_encrypted_file(&mut source_file, &mut destination_file, encryptor)?;
45
46 generated_key.cleanup();
47
48 Ok(())
49}
50
51pub fn decrypt_file(
52 source_path: &str,
53 target_path: &str,
54 password: &str,
55) -> Result<(), Box<dyn Error>> {
56 let mut version = [0u8; 1];
57 let mut salt = [0u8; 32];
58 let mut nonce = [0u8; 19];
59
60 let mut source_file = File::open(source_path)?;
61 let mut target_file = File::create(target_path)?;
62
63 let mut bytes_read = source_file.read(&mut version)?;
64 if bytes_read != version.len() {
65 return Err(Box::new(DecryptionError::new(
66 "Invalid file format".to_string(),
67 )));
68 }
69
70 bytes_read = source_file.read(&mut salt)?;
71 if bytes_read != salt.len() {
72 return Err(Box::new(DecryptionError::new(
73 "Invalid file format".to_string(),
74 )));
75 }
76
77 bytes_read = source_file.read(&mut nonce)?;
78 if bytes_read != nonce.len() {
79 return Err(Box::new(DecryptionError::new(
80 "Invalid file format".to_string(),
81 )));
82 }
83
84 let mut generated_key = keygen::derive_key(password, salt.to_vec(), nonce.to_vec())?;
85
86 let aead = XChaCha20Poly1305::new(generated_key.key[..32].as_ref().into());
87 let decryptor: Decryptor<Cypher, StreamBE32<Cypher>> =
88 stream::DecryptorBE32::from_aead(aead, generated_key.nonce.as_slice().into());
89
90 write_decrypted_file(&mut source_file, &mut target_file, decryptor)?;
91
92 generated_key.cleanup();
93
94 Ok(())
95}
96
97fn write_decrypted_file(
98 src: &mut File,
99 target: &mut File,
100 mut decryptor: Decryptor<Cypher, StreamBE32<Cypher>>,
101) -> Result<(), Box<dyn Error>> {
102 const DECRYPTION_BUFFER_SIZE: usize = BUFFER_SIZE + AUTHENTICATION_TAG_SIZE;
103 let mut buffer = [0u8; DECRYPTION_BUFFER_SIZE];
104
105 loop {
106 let bytes_read = src.read(&mut buffer)?;
107 let all_bytes_read = bytes_read < DECRYPTION_BUFFER_SIZE;
108 let buffer_slice = &buffer[..bytes_read];
109
110 if all_bytes_read {
111 let plain_text = decryptor
112 .decrypt_last(buffer_slice)
113 .map_err(|e| DecryptionError::new(e.to_string()))?;
114 target.write(&plain_text)?;
115 return Ok(());
116 } else {
117 let plain_text = decryptor
118 .decrypt_next(buffer_slice)
119 .map_err(|e| DecryptionError::new(e.to_string()))?;
120 target.write(&plain_text)?;
121 }
122 }
123}
124
125fn write_encrypted_file(
126 src: &mut File,
127 target: &mut File,
128 mut encryptor: Encryptor<Cypher, StreamBE32<Cypher>>,
129) -> Result<(), Box<dyn Error>> {
130 let mut buffer = [0u8; BUFFER_SIZE];
131
132 loop {
133 let bytes_read = src.read(&mut buffer)?;
134 let all_bytes_read = bytes_read < BUFFER_SIZE;
135 let buffer_slice = &buffer[..bytes_read];
136
137 if all_bytes_read {
138 let cipher_text = encryptor
139 .encrypt_last(buffer_slice)
140 .map_err(|e| EncryptionError::new(e.to_string()))?;
141 target.write(&cipher_text)?;
142 return Ok(());
143 } else {
144 let cipher_text = encryptor
145 .encrypt_next(buffer_slice)
146 .map_err(|e| EncryptionError::new(e.to_string()))?;
147 target.write(&cipher_text)?;
148 }
149 }
150}
151
152fn write_header(file: &mut File, salt: &[u8], nonce: &[u8]) -> Result<(), Box<dyn Error>> {
153 let version_buffer: [u8; 1] = [VERSION];
154 file.write(&version_buffer)?;
155
156 file.write(salt)?;
157 file.write(nonce)?;
158
159 Ok(())
160}