ncrypt_me/
lib.rs

1//! ncrypt_me - Secure Data Encryption
2//!
3//!
4//! ## How the Data is Encrypted
5//!
6//! Given some `Credentials` (username and password):
7//!
8//! - **Hashing**: Both the password and username are hashed using **Argon2**.
9//!   - The resulting hash of the **password** is used as the **key** for the **XChaCha20Poly1305** cipher.
10//!   - The resulting hash of the **username** is used as the **Additional Authenticated Data (AAD)** for the cipher.
11//!
12//! - **Encryption**: With the key and AAD set, the data is encrypted using the **XChaCha20Poly1305** cipher.
13//!
14//! - **Output**: The encrypted data is then returned.
15//!
16//! ### Example
17//!
18//!
19//! ```
20//! use ncrypt_me::{encrypt_data, decrypt_data, Credentials, Argon2, secure_types::{SecureString, SecureBytes}};
21//!
22//! let exposed_data: Vec<u8> = vec![1, 2, 3, 4];
23//! let credentials = Credentials::new(
24//!  SecureString::from("username"),
25//!  SecureString::from("password"),
26//!  SecureString::from("password"),
27//! );
28//!
29//! // I don't recommend using such low values, this is just an example
30//! 
31//! let m_cost = 24_000;
32//! let t_cost = 3;
33//! let p_cost = 4;
34//!
35//! let argon2 = Argon2::new(m_cost, t_cost, p_cost);
36//! let secure_data = SecureBytes::from_vec(exposed_data.clone()).unwrap();
37//! let encrypted_data = encrypt_data(argon2, secure_data, credentials.clone()).unwrap();
38//!
39//! let decrypted_data = decrypt_data(encrypted_data, credentials).unwrap();
40//!
41//! decrypted_data.slice_scope(|decrypted_slice| {
42//!  assert_eq!(&exposed_data, decrypted_slice);
43//! });
44//! ```
45
46
47pub mod credentials;
48pub mod decrypt;
49pub mod encrypt;
50pub mod error;
51
52pub use secure_types;
53pub use zeroize;
54
55pub use credentials::Credentials;
56pub use decrypt::decrypt_data;
57pub use encrypt::encrypt_data;
58
59use bincode::{Decode, Encode, config::standard, decode_from_slice};
60use error::{Error, map_argon2_error};
61use secure_types::{SecureBytes, SecureString};
62use zeroize::Zeroize;
63
64use argon2_sys::argon2_hash;
65
66const HEADER_LEN: usize = 8;
67const ENCRYPTED_INFO_START: usize = 12;
68pub const RECOMMENDED_SALT_LEN: usize = 64;
69pub const RECOMMENDED_HASH_LENGTH: u64 = 64;
70
71/// Argon2 primitive type: variants of the algorithm.
72#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default, Ord, Encode, Decode)]
73pub enum Algorithm {
74   /// Optimizes against GPU cracking attacks but vulnerable to side-channels.
75   ///
76   /// Accesses the memory array in a password dependent order, reducing the
77   /// possibility of time–memory tradeoff (TMTO) attacks.
78   Argon2d = 0,
79
80   /// Optimized to resist side-channel attacks.
81   ///
82   /// Accesses the memory array in a password independent order, increasing the
83   /// possibility of time-memory tradeoff (TMTO) attacks.
84   Argon2i = 1,
85
86   /// Hybrid that mixes Argon2i and Argon2d passes (*default*).
87   ///
88   /// Uses the Argon2i approach for the first half pass over memory and
89   /// Argon2d approach for subsequent passes. This effectively places it in
90   /// the "middle" between the other two: it doesn't provide as good
91   /// TMTO/GPU cracking resistance as Argon2d, nor as good of side-channel
92   /// resistance as Argon2i, but overall provides the most well-rounded
93   /// approach to both classes of attacks.
94   #[default]
95   Argon2id = 2,
96}
97
98/// Version of the algorithm.
99#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Encode, Decode)]
100#[repr(u32)]
101pub enum Version {
102   /// Version 16 (0x10 in hex)
103   ///
104   /// Performs overwrite internally
105   V0x10 = 0x10,
106
107   /// Version 19 (0x13 in hex, default)
108   ///
109   /// Performs XOR internally
110   #[default]
111   V0x13 = 0x13,
112}
113
114pub(crate) fn extract_encrypted_info_and_data(
115   encrypted_data: &[u8],
116) -> Result<(Vec<u8>, Vec<u8>), Error> {
117   let encrypted_info_length = u32::from_le_bytes(
118      encrypted_data[HEADER_LEN..ENCRYPTED_INFO_START]
119         .try_into()
120         .map_err(|_| Error::EncryptedInfo)?,
121   );
122
123   let encrypted_info_end = ENCRYPTED_INFO_START + (encrypted_info_length as usize);
124   let encrypted_info = &encrypted_data[ENCRYPTED_INFO_START..encrypted_info_end];
125   let encrypted_data = &encrypted_data[encrypted_info_end..];
126   Ok((encrypted_info.to_vec(), encrypted_data.to_vec()))
127}
128
129#[derive(Default, Clone, Debug, Encode, Decode)]
130pub struct EncryptedInfo {
131   pub password_salt: Vec<u8>,
132   pub username_salt: Vec<u8>,
133   pub cipher_nonce: Vec<u8>,
134   pub argon2: Argon2,
135}
136
137impl EncryptedInfo {
138   pub fn new(
139      password_salt: Vec<u8>,
140      username_salt: Vec<u8>,
141      cipher_nonce: Vec<u8>,
142      argon2: Argon2,
143   ) -> Self {
144      Self {
145         password_salt,
146         username_salt,
147         cipher_nonce,
148         argon2,
149      }
150   }
151
152   pub fn from_encrypted_data(data: &[u8]) -> Result<Self, Error> {
153      let (encrypted_info, _) = extract_encrypted_info_and_data(data)?;
154
155      let info: (EncryptedInfo, usize) = decode_from_slice(&encrypted_info, standard())
156         .map_err(|e| Error::DecodingFailed(e.to_string()))?;
157
158      Ok(info.0)
159   }
160}
161
162/// Argon2 instance
163#[derive(Default, Clone, Debug, Encode, Decode)]
164pub struct Argon2 {
165   pub m_cost: u32,
166   pub t_cost: u32,
167   pub p_cost: u32,
168   pub hash_length: u64,
169   /// By default we use the Argon2id
170   pub algorithm: Algorithm,
171   /// By default we use the version 0x13
172   pub version: Version,
173}
174
175impl Argon2 {
176   pub fn new(m_cost: u32, t_cost: u32, p_cost: u32) -> Self {
177      Self {
178         m_cost,
179         t_cost,
180         p_cost,
181         hash_length: RECOMMENDED_HASH_LENGTH,
182         ..Default::default()
183      }
184   }
185
186   pub fn with_algorithm(mut self, algorithm: Algorithm) -> Self {
187      self.algorithm = algorithm;
188      self
189   }
190
191   pub fn with_version(mut self, version: Version) -> Self {
192      self.version = version;
193      self
194   }
195
196   pub fn with_hash_length(mut self, hash_length: u64) -> Self {
197      self.hash_length = hash_length;
198      self
199   }
200
201   /// Hashes the given password
202   ///
203   /// ## Arguments
204   ///
205   /// - `password` - The password to hash
206   /// - `salt` - The salt to use for hashing
207   /// - `params` - The Argon2 parameters to use for hashing
208   ///
209   ///
210   /// ## Returns
211   ///
212   /// The hash of the password in its raw byte form
213   pub fn hash_password(
214      &self,
215      password: &SecureString,
216      mut salt: Vec<u8>,
217   ) -> Result<SecureBytes, Error> {
218      let mut hash_buffer = vec![0u8; self.hash_length as usize];
219      let (hash_buffer_ptr, hash_buffer_len) = (
220         hash_buffer.as_mut_ptr() as *mut libc::c_void,
221         hash_buffer.len(),
222      );
223
224      let (salt_ptr, saltlen) = (salt.as_ptr() as *const libc::c_void, salt.len());
225
226      let code = password.str_scope(|password| {
227         let (password_ptr, password_len) = (
228            password.as_bytes().as_ptr() as *const libc::c_void,
229            password.len(),
230         );
231
232         unsafe {
233            argon2_hash(
234               self.t_cost,
235               self.m_cost,
236               self.p_cost,
237               password_ptr,
238               password_len,
239               salt_ptr,
240               saltlen,
241               hash_buffer_ptr,
242               hash_buffer_len,
243               core::ptr::null_mut(),
244               0,
245               self.algorithm as u32,
246               self.version as u32,
247            )
248         }
249      });
250
251      salt.zeroize();
252
253      if code != 0 {
254         return Err(Error::Argon2(map_argon2_error(code)));
255      }
256
257      let secured_buffer =
258         SecureBytes::from_vec(hash_buffer).map_err(|e| Error::Custom(e.to_string()))?;
259      Ok(secured_buffer)
260   }
261}
262
263// Argon2 Presets
264impl Argon2 {
265
266   /// Not recommended
267   pub fn very_fast() -> Self {
268      Self {
269         m_cost: 128_000,
270         t_cost: 8,
271         p_cost: 64,
272         hash_length: RECOMMENDED_HASH_LENGTH,
273         ..Default::default()
274      }
275   }
276
277   /// Not recommended
278   pub fn fast() -> Self {
279      Self {
280         m_cost: 256_000,
281         t_cost: 8,
282         p_cost: 64,
283         hash_length: RECOMMENDED_HASH_LENGTH,
284         ..Default::default()
285      }
286   }
287
288   pub fn balanced() -> Self {
289      Self {
290         m_cost: 1024_000,
291         t_cost: 16,
292         p_cost: 64,
293         hash_length: RECOMMENDED_HASH_LENGTH,
294         ..Default::default()
295      }
296   }
297
298   pub fn slow() -> Self {
299      Self {
300         m_cost: 2048_000,
301         t_cost: 16,
302         p_cost: 64,
303         hash_length: RECOMMENDED_HASH_LENGTH,
304         ..Default::default()
305      }
306   }
307
308   pub fn very_slow() -> Self {
309      Self {
310         m_cost: 3072_000,
311         t_cost: 24,
312         p_cost: 64,
313         hash_length: RECOMMENDED_HASH_LENGTH,
314         ..Default::default()
315      }
316   }
317}
318
319#[cfg(test)]
320mod tests {
321   use super::*;
322   use secure_types::{SecureBytes, SecureString};
323
324   #[test]
325   fn can_encrypt_decrypt() {
326      let exposed_data: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
327      let credentials = Credentials::new(
328         SecureString::from("username"),
329         SecureString::from("password"),
330         SecureString::from("password"),
331      );
332
333      let m_cost = 24_000;
334      let t_cost = 3;
335      let p_cost = 1;
336
337      let argon2 = Argon2::new(m_cost, t_cost, p_cost);
338
339      let secure_data = SecureBytes::from_vec(exposed_data.clone()).unwrap();
340
341      let encrypted_data = encrypt_data(argon2, secure_data, credentials.clone()).unwrap();
342      let decrypted_data = decrypt_data(encrypted_data, credentials).unwrap();
343
344      decrypted_data.slice_scope(|decrypted_data| {
345         assert_eq!(exposed_data, decrypted_data);
346      });
347   }
348}