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