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