encrypt.rs
1 use super::*; 2 3 use bincode::{config::standard, encode_to_vec}; 4 use chacha20poly1305::{ 5 AeadCore, KeyInit, XChaCha20Poly1305, 6 aead::{Aead, OsRng, Payload, generic_array::GenericArray, rand_core::RngCore}, 7 }; 8 use secure_types::SecureBytes; 9 10 /* 11 ██████████████████████████████████████████████████████████████████████████████ 12 █ █ 13 █ nCrypt File Format █ 14 █ █ 15 █ ┌───────────┬──────────────────┬──────────────────┬───────────────┐ █ 16 █ │ Header │ EncryptedInfo Len│ EncryptedInfo │ Encrypted Data│ █ 17 █ │ 8 bytes │ 4 bytes │ Dyn Size │ Dyn Size │ █ 18 █ └───────────┴──────────────────┴──────────────────┴───────────────┘ █ 19 █ █ 20 █ █ 21 ██████████████████████████████████████████████████████████████████████████████ 22 */ 23 24 /// File Header 25 pub const HEADER: &[u8; 8] = b"nCrypt1\0"; 26 27 /// Encrypts the given data 28 /// 29 /// ### Arguments 30 /// 31 /// - `argon2` - The Argon2 instance to use for the password hashing 32 /// - `data` - The data to encrypt 33 /// - `credentials` - The credentials to use for encryption 34 pub fn encrypt_data( 35 argon2: Argon2, 36 data: SecureBytes, 37 credentials: Credentials, 38 ) -> Result<Vec<u8>, Error> { 39 let (encrypted_data, info) = encrypt(argon2, credentials, data)?; 40 41 let encoded_info = 42 encode_to_vec(&info, standard()).map_err(|e| Error::EncodingFailed(e.to_string()))?; 43 44 // Construct the file format 45 let mut result = Vec::new(); 46 47 // Append the header 48 result.extend_from_slice(HEADER); 49 50 // Append the EncryptedInfo Length 51 let info_length = encoded_info.len() as u32; 52 result.extend_from_slice(&info_length.to_le_bytes()); 53 54 // Append the EncryptedInfo 55 result.extend_from_slice(&encoded_info); 56 57 // Append the encrypted Data 58 result.extend_from_slice(&encrypted_data); 59 60 Ok(result) 61 } 62 63 fn encrypt( 64 argon2: Argon2, 65 credentials: Credentials, 66 data: SecureBytes, 67 ) -> Result<(Vec<u8>, EncryptedInfo), Error> { 68 credentials.is_valid()?; 69 70 if argon2.hash_length < 32 { 71 return Err(Error::HashLength); 72 } 73 74 let mut password_salt = vec![0u8; RECOMMENDED_SALT_LEN]; 75 let mut username_salt = vec![0u8; RECOMMENDED_SALT_LEN]; 76 77 OsRng 78 .try_fill_bytes(&mut password_salt) 79 .map_err(|e| Error::Custom(e.to_string()))?; 80 OsRng 81 .try_fill_bytes(&mut username_salt) 82 .map_err(|e| Error::Custom(e.to_string()))?; 83 84 let cipher_nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng); 85 86 let password_hash = argon2.hash_password(&credentials.password, password_salt.clone())?; 87 let username_hash = argon2.hash_password(&credentials.username, username_salt.clone())?; 88 89 data.unlock_slice(|data| { 90 let mut aad = username_hash.unlock_slice(|bytes| bytes.to_vec()); 91 92 let payload = Payload { 93 msg: data, 94 aad: &aad, 95 }; 96 97 let cipher = xchacha20_poly_1305(password_hash); 98 99 let encrypted_data_res = cipher.encrypt(&cipher_nonce, payload); 100 aad.zeroize(); 101 102 let encrypted_data = match encrypted_data_res { 103 Ok(data) => data, 104 Err(e) => { 105 return Err(Error::EncryptionFailed(e.to_string())); 106 } 107 }; 108 109 let info = EncryptedInfo::new( 110 password_salt, 111 username_salt, 112 cipher_nonce.to_vec(), 113 argon2, 114 ); 115 116 Ok((encrypted_data, info)) 117 }) 118 } 119 120 pub(crate) fn xchacha20_poly_1305(hash_output: SecureBytes) -> XChaCha20Poly1305 { 121 let mut key = hash_output.unlock_slice(|bytes| *GenericArray::from_slice(&bytes[..32])); 122 123 let cipher = XChaCha20Poly1305::new(&key); 124 key.zeroize(); 125 cipher 126 }