Skip to main content

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.unlock_slice(|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;
52
53pub use credentials::Credentials;
54pub use decrypt::decrypt_data;
55pub use encrypt::{HEADER, HEADER_02, encrypt_data};
56
57use error::Error;
58
59pub use argon2_rs::Argon2;
60
61const HEADER_LEN: usize = 8;
62const ENCRYPTED_INFO_START: usize = 12;
63pub const RECOMMENDED_SALT_LEN: usize = 64;
64
65pub(crate) fn extract_encrypted_info_and_data(data: &[u8]) -> Result<(Vec<u8>, Vec<u8>), Error> {
66   if &data[0..8] != HEADER_02 {
67      return Err(Error::InvalidFileFormat);
68   }
69
70   if &data[0..8] == HEADER {
71      return Err(Error::VersionMismatch);
72   }
73
74   let encrypted_info_length = u32::from_le_bytes(
75      data[HEADER_LEN..ENCRYPTED_INFO_START]
76         .try_into()
77         .map_err(|_| Error::EncryptedInfo)?,
78   );
79
80   let encrypted_info_end = ENCRYPTED_INFO_START + (encrypted_info_length as usize);
81   let encrypted_info = &data[ENCRYPTED_INFO_START..encrypted_info_end];
82   let encrypted_data = &data[encrypted_info_end..];
83   Ok((encrypted_info.to_vec(), encrypted_data.to_vec()))
84}
85
86#[derive(Default, Clone, Debug)]
87pub struct EncryptedInfo {
88   pub password_salt: Vec<u8>,
89   pub username_salt: Vec<u8>,
90   pub cipher_nonce: Vec<u8>,
91   pub argon2: Argon2,
92}
93
94impl EncryptedInfo {
95   pub fn new(
96      password_salt: Vec<u8>,
97      username_salt: Vec<u8>,
98      cipher_nonce: Vec<u8>,
99      argon2: Argon2,
100   ) -> Self {
101      Self {
102         password_salt,
103         username_salt,
104         cipher_nonce,
105         argon2,
106      }
107   }
108
109   pub fn encode(&self) -> Vec<u8> {
110      let mut data = Vec::new();
111      data.extend_from_slice(&self.password_salt);
112      data.extend_from_slice(&self.username_salt);
113      data.extend_from_slice(&self.cipher_nonce);
114      data.extend_from_slice(&self.argon2.encode());
115      data
116   }
117
118   pub fn from_encrypted_data(data: &[u8]) -> Result<Self, Error> {
119      let (encrypted_info, _) = extract_encrypted_info_and_data(data)?;
120
121      let salt_len = RECOMMENDED_SALT_LEN;
122      let password_salt = encrypted_info[0..salt_len].to_vec();
123      let username_salt = encrypted_info[salt_len..(salt_len * 2)].to_vec();
124      let cipher_nonce_start = salt_len * 2;
125      let cipher_nonce = encrypted_info[cipher_nonce_start..(cipher_nonce_start + 24)].to_vec();
126      let argon2_start = cipher_nonce_start + 24;
127      let argon2 = Argon2::decode(&encrypted_info[argon2_start..])?;
128
129      let info = EncryptedInfo {
130         password_salt,
131         username_salt,
132         cipher_nonce,
133         argon2,
134      };
135
136      Ok(info)
137   }
138}
139
140#[cfg(test)]
141mod tests {
142   use super::*;
143   use secure_types::{SecureBytes, SecureString};
144
145   #[test]
146   fn can_encrypt_decrypt() {
147      let exposed_data: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
148      let credentials = Credentials::new(
149         SecureString::from("username"),
150         SecureString::from("password"),
151         SecureString::from("password"),
152      );
153
154      let m_cost = 24_000;
155      let t_cost = 3;
156      let p_cost = 1;
157
158      let argon2 = Argon2::new(m_cost, t_cost, p_cost);
159
160      let secure_data = SecureBytes::from_vec(exposed_data.clone()).unwrap();
161
162      let encrypted_data = encrypt_data(argon2, secure_data, credentials.clone()).unwrap();
163      let decrypted_data = decrypt_data(encrypted_data, credentials).unwrap();
164
165      decrypted_data.unlock_slice(|decrypted_data| {
166         assert_eq!(exposed_data, decrypted_data);
167      });
168   }
169}