sharing/
crypto.rs

1/*
2 * Authors: Jorge.A Duran & Mario Gónzalez
3 * Company: pispas Technologies SL
4 * Date: April 23, 2023
5 * Description: Cryptography module for sharing
6 */
7use 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    // Load from environment variable if exists
155    if let Ok(key) = std::env::var("SIGNER_ENCRYPTION_KEY") {
156        return Ok(key);
157    }
158
159    // Create new key
160    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}