ncrypt_me/
encrypt.rs

1use super::*;
2
3use bincode::{config::standard, encode_to_vec};
4use chacha20poly1305::{
5   AeadCore, KeyInit, XChaCha20Poly1305,
6   aead::{Aead, OsRng, Payload, generic_array::GenericArray, rand_core::RngCore},
7};
8use 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
25pub 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
34pub 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
63fn 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 mut aad = credentials
87      .username
88      .unlock_str(|username_str| argon2.hash_password(&username_str, username_salt.clone()))
89      .map_err(|e| Error::Custom(e.to_string()))?;
90
91   let password_hash = credentials
92      .password
93      .unlock_str(|password_str| argon2.hash_password(&password_str, password_salt.clone()))
94      .map_err(|e| Error::Custom(e.to_string()))?;
95
96   data.unlock_slice(|data| {
97      let payload = Payload {
98         msg: data,
99         aad: &aad,
100      };
101
102      let cipher = xchacha20_poly_1305(password_hash);
103
104      let encrypted_data_res = cipher.encrypt(&cipher_nonce, payload);
105      aad.zeroize();
106
107      let encrypted_data = match encrypted_data_res {
108         Ok(data) => data,
109         Err(e) => {
110            return Err(Error::EncryptionFailed(e.to_string()));
111         }
112      };
113
114      let info = EncryptedInfo::new(
115         password_salt,
116         username_salt,
117         cipher_nonce.to_vec(),
118         argon2,
119      );
120
121      Ok((encrypted_data, info))
122   })
123}
124
125pub(crate) fn xchacha20_poly_1305(mut hash_output: Vec<u8>) -> XChaCha20Poly1305 {
126   let mut key = GenericArray::clone_from_slice(&hash_output[..32]);
127   hash_output.zeroize();
128
129   let cipher = XChaCha20Poly1305::new(&key);
130   key.zeroize();
131   cipher
132}