/ src / encrypt.rs
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  }