security_cam_common/
encryption.rs

1use std::io::ErrorKind;
2use actix_web::web::{Bytes};
3use aes_gcm::aead::stream;
4use aes_gcm::aead::{Key, KeyInit};
5
6use aes_gcm::Aes256Gcm;
7use argon2::Argon2;
8use argon2::password_hash::rand_core::OsRng;
9use argon2::password_hash::SaltString;
10use async_stream::stream;
11use base64::{Engine, engine};
12use futures_core::Stream;
13use shuttle_runtime::tokio;
14use shuttle_runtime::tokio::io::AsyncReadExt;
15
16
17const BUFFER_LEN: usize = 500;
18const NONCE_LEN: usize = 16;
19trait SizedError: std::error::Error + std::marker::Sized {}
20
21pub fn encrypt_stream(key: Key<Aes256Gcm>, salt: SaltString, mut file: tokio::fs::File,) -> impl Stream<Item=Result<Vec<u8> , std::io::Error>>  {
22    let s = stream! {
23            let b64_decoder = base64::engine::general_purpose::STANDARD_NO_PAD;
24            let nonce = b64_decoder.decode(salt.as_str()).map_err(|e| {
25                std::io::Error::new(ErrorKind::Other, e.to_string())
26            })?;
27
28            let mut buffer = [0u8; BUFFER_LEN];
29
30            let mut encryptor = stream::EncryptorBE32::from_aead(
31                Aes256Gcm::new(&key),
32                nonce[0..7].into()
33            );
34            let mut last_chunk = Vec::new(); // stores the last file chunk that may be less than len BUFFER_LEN
35            yield Ok(nonce); // nonce will be appended to the beginning of the file
36            loop {
37                let read_count = file.read(&mut buffer).await?;
38                if read_count == BUFFER_LEN {
39                    let encrypted = encryptor.encrypt_next(&buffer[..])
40                        .map_err(|e| std::io::Error::new(ErrorKind::Other, e.to_string()));
41                    yield encrypted;
42                } else if read_count == 0 {
43                    break;
44                } else {
45                    last_chunk = buffer[..read_count].to_vec();
46                }
47            }
48            let encrypted = (encryptor)
49                .encrypt_last(&last_chunk[..])
50                .map_err(|e| std::io::Error::new(ErrorKind::Other, e.to_string()));
51            yield encrypted;
52        };
53    s // return the stream
54}
55
56
57pub fn decrypt_stream(mut file: tokio::fs::File, password: String) -> impl Stream<Item=Result<Bytes, Box<dyn std::error::Error + 'static>>>  {
58    let s = stream! {
59        // read in the salt
60        let mut buffer = [0u8; BUFFER_LEN+16];
61        let mut salt = [0u8; NONCE_LEN];
62        file.read_exact(&mut salt).await?;
63        let salt = salt.to_vec();
64
65        // generate key from salt and password
66        let key = generate_keystream(&password,&salt)?;
67        let mut decryptor = stream::DecryptorBE32::from_aead(
68            Aes256Gcm::new(&key),
69            (&salt[0..7]).into()
70        );
71
72        // will store the last chunk of data that could be less than BUFFER_LEN
73        let mut last_chunk = Vec::new();
74        loop {
75            let read_count = file.read(&mut buffer).await?;
76            if read_count == BUFFER_LEN+16 {
77                let decrypted: Result<Vec<u8>, Box<dyn std::error::Error>> = decryptor.decrypt_next(&buffer[..]).map_err(|e|
78                    {
79                        println!("got an error: {:?}",e);
80                        e.to_string().into()
81                    });
82                yield Ok(Bytes::from(decrypted?));
83            }else if read_count == 0 {
84                println!("no bytes read");
85                break;
86            } else {
87                last_chunk = buffer[..read_count].to_vec();
88            }
89        }
90        let decrypted: Result<Vec<u8>, Box<dyn std::error::Error>> = (decryptor)
91        .decrypt_last(&last_chunk[..])
92        .map_err(|e| e.to_string().into());
93        yield Ok(Bytes::from(decrypted?));
94    };
95    s
96}
97
98
99/// returns (key, salt)
100pub fn generate_key(password: &str) -> Result<(Key<Aes256Gcm>,SaltString), Box<dyn std::error::Error>> {
101    let mut key_out = [0u8; 32];
102    let salt = SaltString::generate(&mut OsRng);
103    let b64_decoder = engine::general_purpose::STANDARD_NO_PAD;
104    let salt_bytes = b64_decoder.decode(salt.as_str())?;
105    Argon2::default().hash_password_into(password.as_bytes(), &salt_bytes, &mut key_out).map_err(|e|e.to_string())?;
106    Ok((key_out.into(),salt ))
107}
108
109/// salt should be made by base64 decoding a SaltString
110pub fn generate_keystream(password:&str,salt: &[u8]) -> Result<Key<Aes256Gcm>, Box<dyn std::error::Error>> {
111    let mut key_out = [0u8; 32];
112    Argon2::default().hash_password_into(password.as_bytes(), salt, &mut key_out).map_err(|e|e.to_string())?;
113    Ok(key_out.into())
114}
115
116
117// /// currently only used for testing purposes
118// fn encrypt_bytes(key: &Key<Aes256Gcm>,salt:SaltString, plaintext: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
119//     let cipher = Aes256Gcm::new(key);
120//     let salt  = salt.to_string();
121//     // nonce only needs to be 12 bytes for aesgcm
122//     Ok(cipher.encrypt(salt.as_bytes()[0..12].into(), plaintext).map_err(|e| {e.to_string()})?)
123// }
124//
125// pub fn decrypt_bytes(key: &Key<Aes256Gcm>,salt:SaltString, ciphertext: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
126//     let cipher = Aes256Gcm::new(key);
127//     Ok(cipher.decrypt(salt.to_string().to_string().as_bytes()[0..12].into(), ciphertext).map_err(|e| {e.to_string()})?)
128// }
129
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use tokio::io::AsyncWriteExt;
135    use tokio_stream::StreamExt;
136
137
138    use std::fs::remove_file;
139
140
141    #[actix_web::test]
142    async fn test_encrypt_decrypt() -> Result<(), Box<dyn std::error::Error>> {
143        let password = "test_password";
144        let (key, salt) = generate_key(password)?;
145
146        let test_data = "This is some test data";
147        let tmp_file_path = "/var/tmp/test_file.txt";
148        let mut tmp_file = tokio::fs::File::create(tmp_file_path).await?;
149        tmp_file.write_all(test_data.as_bytes()).await?;
150
151        let file = tokio::fs::File::open(tmp_file_path).await?;
152        // let decryptor = EncryptDecrypt { key: Some(key), salt: Some(salt.clone()), file };
153        let mut encrypted_stream = Box::pin(encrypt_stream(key, salt, file));
154
155        let tmp_file_path2 = "/var/tmp/test_file2.txt";
156        let mut tmp_file2 = tokio::fs::File::create(tmp_file_path2).await?;
157        while let Some(chunk) = encrypted_stream.next().await {
158            tmp_file2.write_all(&chunk.unwrap()).await?;
159        }
160
161        let file2 = tokio::fs::File::open(tmp_file_path2).await?;
162        // let decryptor2 = EncryptDecrypt { key: None, salt: None, file: file2 };
163        let mut decrypted_stream = Box::pin(decrypt_stream(file2, "test_password".to_owned()));
164
165        let mut decrypted_data = Vec::new();
166        while let Some(chunk) = decrypted_stream.next().await {
167            let chunk = chunk?;
168            decrypted_data.extend_from_slice(&chunk);
169        }
170
171        assert_eq!(test_data.as_bytes(), decrypted_data.as_slice());
172
173        remove_file(tmp_file_path)?;
174        remove_file(tmp_file_path2)?;
175
176        Ok(())
177    }
178
179}
180
181