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::encrypt_data;
57
58use bincode::{Decode, Encode, config::standard, decode_from_slice};
59use error::Error;
60use zeroize::Zeroize;
61
62pub use argon2_rs::Argon2;
63
64const HEADER_LEN: usize = 8;
65const ENCRYPTED_INFO_START: usize = 12;
66pub const RECOMMENDED_SALT_LEN: usize = 64;
67
68pub(crate) fn extract_encrypted_info_and_data(
69   encrypted_data: &[u8],
70) -> Result<(Vec<u8>, Vec<u8>), Error> {
71   let encrypted_info_length = u32::from_le_bytes(
72      encrypted_data[HEADER_LEN..ENCRYPTED_INFO_START]
73         .try_into()
74         .map_err(|_| Error::EncryptedInfo)?,
75   );
76
77   let encrypted_info_end = ENCRYPTED_INFO_START + (encrypted_info_length as usize);
78   let encrypted_info = &encrypted_data[ENCRYPTED_INFO_START..encrypted_info_end];
79   let encrypted_data = &encrypted_data[encrypted_info_end..];
80   Ok((encrypted_info.to_vec(), encrypted_data.to_vec()))
81}
82
83#[derive(Default, Clone, Debug, Encode, Decode)]
84pub struct EncryptedInfo {
85   pub password_salt: Vec<u8>,
86   pub username_salt: Vec<u8>,
87   pub cipher_nonce: Vec<u8>,
88   pub argon2: Argon2,
89}
90
91impl EncryptedInfo {
92   pub fn new(
93      password_salt: Vec<u8>,
94      username_salt: Vec<u8>,
95      cipher_nonce: Vec<u8>,
96      argon2: Argon2,
97   ) -> Self {
98      Self {
99         password_salt,
100         username_salt,
101         cipher_nonce,
102         argon2,
103      }
104   }
105
106   pub fn from_encrypted_data(data: &[u8]) -> Result<Self, Error> {
107      let (encrypted_info, _) = extract_encrypted_info_and_data(data)?;
108
109      let info: (EncryptedInfo, usize) = decode_from_slice(&encrypted_info, standard())
110         .map_err(|e| Error::DecodingFailed(e.to_string()))?;
111
112      Ok(info.0)
113   }
114}
115
116#[cfg(test)]
117mod tests {
118   use super::*;
119   use secure_types::{SecureBytes, SecureString};
120
121   #[test]
122   fn can_encrypt_decrypt() {
123      let exposed_data: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
124      let credentials = Credentials::new(
125         SecureString::from("username"),
126         SecureString::from("password"),
127         SecureString::from("password"),
128      );
129
130      let m_cost = 24_000;
131      let t_cost = 3;
132      let p_cost = 1;
133
134      let argon2 = Argon2::new(m_cost, t_cost, p_cost);
135
136      let secure_data = SecureBytes::from_vec(exposed_data.clone()).unwrap();
137
138      let encrypted_data = encrypt_data(argon2, secure_data, credentials.clone()).unwrap();
139      let decrypted_data = decrypt_data(encrypted_data, credentials).unwrap();
140
141      decrypted_data.unlock_slice(|decrypted_data| {
142         assert_eq!(exposed_data, decrypted_data);
143      });
144   }
145}