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 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.slice_scope(|data| {
90      let mut aad = username_hash.slice_scope(|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
120pub(crate) fn xchacha20_poly_1305(hash_output: SecureBytes) -> XChaCha20Poly1305 {
121   let mut key = hash_output.slice_scope(|bytes| *GenericArray::from_slice(&bytes[..32]));
122
123   let cipher = XChaCha20Poly1305::new(&key);
124   key.zeroize();
125   cipher
126}