1use anyhow::anyhow;
8use chacha20poly1305::KeyInit;
9use chacha20poly1305::{aead::stream, XChaCha20Poly1305};
10use rand::{rngs::OsRng, RngCore};
11use zeroize::Zeroize;
12use std::fs::File;
13use std::io::{Read, Write};
14use base64::{Engine as _, engine::general_purpose};
15use crate::{PisPasResult, KEY_FILE_NAME};
16
17const SALT_SIZE: usize = 32;
18const NONCE_SIZE: usize = 19;
19
20fn argon2_config<'a>() -> argon2::Config<'a> {
21 argon2::Config {
22 variant: argon2::Variant::Argon2id,
23 hash_length: 32,
24 lanes: 8,
25 mem_cost: 16 * 1024,
26 time_cost: 8,
27 ..Default::default()
28 }
29}
30
31pub fn encrypt(data: &Vec<u8>, password: &str) -> Result<Vec<u8>, anyhow::Error> {
32 let argon2_config = argon2_config();
33
34 let mut salt = [0u8; SALT_SIZE];
35 let mut nonce = [0u8; NONCE_SIZE];
36 OsRng.fill_bytes(&mut salt);
37 OsRng.fill_bytes(&mut nonce);
38
39 let mut key = argon2::hash_raw(password.as_bytes(), &salt, &argon2_config)?;
40
41 let aead = XChaCha20Poly1305::new(key[..SALT_SIZE].as_ref().into());
42 let mut stream_encryptor = stream::EncryptorBE32::from_aead(aead, nonce.as_ref().into());
43
44 let mut target_vec: Vec<u8> = Vec::new();
45 target_vec.append(&mut salt.to_vec());
46 target_vec.append(&mut nonce.to_vec());
47
48 let mut ciphertext = stream_encryptor
49 .encrypt_next(data.as_slice())
50 .map_err(|err| anyhow!("Encrypting large file: {}", err))?;
51 target_vec.append(&mut ciphertext);
52
53 salt.zeroize();
54 nonce.zeroize();
55 key.zeroize();
56
57 Ok(target_vec)
58}
59
60pub fn decrypt(encrypted: &Vec<u8>, password: &str) -> Result<Vec<u8>, anyhow::Error> {
61 let mut salt = encrypted[0..SALT_SIZE].to_vec();
62 if SALT_SIZE != salt.len() {
63 return Err(anyhow!("Error reading salt."));
64 }
65
66 let mut nonce = encrypted[SALT_SIZE..SALT_SIZE + NONCE_SIZE].to_vec();
67 if NONCE_SIZE != nonce.len() {
68 return Err(anyhow!("Error reading nonce."));
69 }
70
71 let argon2_config = argon2_config();
72
73 let mut key = argon2::hash_raw(password.as_bytes(), &salt, &argon2_config)?;
74 let aead = XChaCha20Poly1305::new(key[..32].as_ref().into());
75 let mut stream_decryptor =
76 stream::DecryptorBE32::from_aead(aead, nonce.as_slice().as_ref().into());
77
78 let plaintext = stream_decryptor
79 .decrypt_next(encrypted[SALT_SIZE + NONCE_SIZE..].to_vec().as_slice())
80 .map_err(|err| anyhow!("Decrypting bytes: {}", err))?;
81
82 salt.zeroize();
83 nonce.zeroize();
84 key.zeroize();
85
86 Ok(plaintext)
87}
88
89pub fn encrypt_text(data: &str, password: &str) -> Result<Vec<u8>, anyhow::Error> {
90 Ok(encrypt(&data.as_bytes().to_vec(), password)?)
91}
92
93pub fn decrypt_text(encrypted: &Vec<u8>, password: &str) -> Result<String, anyhow::Error> {
94 let decrypted = decrypt(encrypted, password)?;
95 Ok(String::from_utf8(decrypted)?)
96}
97
98pub fn encrypt_text_to_base64(data: &str, password: &str) -> Result<String, anyhow::Error> {
99 let encrypted = encrypt_text(data, password)?;
100 Ok(general_purpose::STANDARD_NO_PAD.encode(&encrypted))
101}
102
103pub fn decrypt_text_from_base64(encrypted: &str, password: &str) -> Result<String, anyhow::Error> {
104 let encrypted = general_purpose::STANDARD_NO_PAD.decode(encrypted.as_bytes())?;
105 Ok(decrypt_text(&encrypted, password)?)
106}
107
108
109pub struct Crypter {
110 key: String,
111}
112
113impl Default for Crypter {
114 fn default() -> Self {
115 Crypter::load().expect("Error loading encryption key")
116 }
117}
118
119impl Crypter {
120
121 pub fn load() -> PisPasResult<Self> {
122 Ok(Self {
123 key: load_encryption_key()?,
124 })
125 }
126
127 pub fn decrypt(&self, encrypted_password: &str) -> PisPasResult<String> {
128 decrypt_text_from_base64(encrypted_password, &self.key)
129 }
130
131 pub fn encrypt(&self, password: &str) -> PisPasResult<String> {
132 encrypt_text_to_base64(password, &self.key)
133 }
134}
135
136
137#[allow(dead_code)]
138pub fn load_encryption_key() -> PisPasResult<String> {
139 let mut file = match File::open(KEY_FILE_NAME) {
140 Ok(file) => file,
141 Err(_) => return create_key_in_file(),
142 };
143
144 let mut key = String::new();
145 if file.read_to_string(&mut key).is_ok() {
146 Ok(key)
147 } else {
148 create_key_in_file()
149 }
150}
151
152fn create_key_in_file() -> PisPasResult<String> {
153
154 if let Ok(key) = std::env::var("SIGNER_ENCRYPTION_KEY") {
156 return Ok(key);
157 }
158
159 let new_key = generate_random_key_128();
161 save_encryption_key(&new_key)?;
162 Ok(new_key)
163}
164
165fn save_encryption_key(key: &str) -> PisPasResult<()> {
166 let mut file = File::create(KEY_FILE_NAME)?;
167 file.write_all(key.as_bytes())?;
168 Ok(())
169}
170
171pub fn generate_random_key_128() -> String {
172 crate::string::generate_random_string(128)
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use lipsum::lipsum;
179 const TEST_PWD: &str = "test password";
180
181 #[test]
182 fn decode_and_encode() {
183 let text = lipsum(1024);
184 let encoded = encrypt(&text.as_bytes().to_vec(), TEST_PWD);
185 assert!(encoded.is_ok());
186 let encoded = encoded.unwrap();
187 let decoded = decrypt(&encoded, TEST_PWD);
188 assert!(decoded.is_ok());
189 let decoded_text = String::from_utf8(decoded.unwrap()).unwrap();
190 assert_eq!(text, decoded_text);
191 }
192
193 #[test]
194 fn should_fail_when_wrong_password() {
195 let text = lipsum(1024);
196 let encoded = encrypt(&text.as_bytes().to_vec(), TEST_PWD);
197 assert!(encoded.is_ok());
198 let encoded = encoded.unwrap();
199 let decoded = decrypt(&encoded, format!("{}asd", TEST_PWD).as_str());
200 assert!(!decoded.is_ok());
201 }
202
203
204 #[test]
205 fn test_crypter() {
206 let crypter = Crypter::load().unwrap();
207 let text = lipsum(1024);
208 let encoded = crypter.encrypt(&text).unwrap();
209 let decoded = crypter.decrypt(&encoded).unwrap();
210 assert_eq!(text, decoded);
211 }
212}