Skip to main content

ncrypt_me/
encrypt.rs

1use super::*;
2
3use chacha20poly1305::{
4   AeadCore, KeyInit, XChaCha20Poly1305,
5   aead::{Aead, OsRng, Payload, generic_array::GenericArray, rand_core::RngCore},
6};
7use secure_types::{SecureBytes, Zeroize};
8
9/*
10██████████████████████████████████████████████████████████████████████████████
11█                                                                            █
12█                           nCrypt File Format                               █
13█                                                                            █
14█    ┌───────────┬──────────────────┬──────────────────┬───────────────┐     █
15█    │   Header  │ EncryptedInfo Len│  EncryptedInfo   │ Encrypted Data│     █
16█    │  8 bytes  │     4 bytes      │  Dyn Size        │ Dyn Size      │     █
17█    └───────────┴──────────────────┴──────────────────┴───────────────┘     █
18█                                                                            █
19█                                                                            █
20██████████████████████████████████████████████████████████████████████████████
21*/
22
23// File Headers
24pub const HEADER: &[u8; 8] = b"nCrypt1\0";
25
26pub const HEADER_02: &[u8; 8] = b"nCrypt2\0";
27
28/// Encrypts the given data
29///
30/// ### Arguments
31///
32/// - `argon2` - The Argon2 instance to use for the password hashing
33/// - `secure_data` - The data to encrypt
34/// - `credentials` - The credentials to use for encryption
35pub fn encrypt_data(
36   argon2: Argon2,
37   secure_data: SecureBytes,
38   credentials: Credentials,
39) -> Result<Vec<u8>, Error> {
40   let (encrypted_data, info) =
41      secure_data.unlock_slice(|data| encrypt(argon2, credentials, data))?;
42
43   let encoded_info = info.encode();
44
45   // Construct the file format
46   let mut result = Vec::new();
47
48   // Append the header
49   result.extend_from_slice(HEADER_02);
50
51   // Append the EncryptedInfo Length
52   let info_length = encoded_info.len() as u32;
53   result.extend_from_slice(&info_length.to_le_bytes());
54
55   // Append the EncryptedInfo
56   result.extend_from_slice(&encoded_info);
57
58   // Append the encrypted Data
59   result.extend_from_slice(&encrypted_data);
60
61   Ok(result)
62}
63
64/// Encrypts the given data
65///
66/// This is the same as `encrypt_data` but takes a reference to the data to encrypt
67///
68/// Use this if the data you want to encrypt is too large to fit in a [SecureBytes]
69///
70/// ### Arguments
71///
72/// - `argon2` - The Argon2 instance to use for the password hashing
73/// - `data` - a reference to the data to encrypt
74/// - `credentials` - The credentials to use for encryption
75pub fn encrypt_data_ref(
76   argon2: Argon2,
77   data: &[u8],
78   credentials: Credentials,
79) -> Result<Vec<u8>, Error> {
80   let (encrypted_data, info) = encrypt(argon2, credentials, data)?;
81
82   let encoded_info = info.encode();
83
84   // Construct the file format
85   let mut result = Vec::new();
86
87   // Append the header
88   result.extend_from_slice(HEADER_02);
89
90   // Append the EncryptedInfo Length
91   let info_length = encoded_info.len() as u32;
92   result.extend_from_slice(&info_length.to_le_bytes());
93
94   // Append the EncryptedInfo
95   result.extend_from_slice(&encoded_info);
96
97   // Append the encrypted Data
98   result.extend_from_slice(&encrypted_data);
99
100   Ok(result)
101}
102
103fn encrypt(
104   argon2: Argon2,
105   credentials: Credentials,
106   data: &[u8],
107) -> Result<(Vec<u8>, EncryptedInfo), Error> {
108   credentials.is_valid()?;
109
110   if argon2.hash_length < 32 {
111      return Err(Error::HashLength);
112   }
113
114   let mut password_salt = vec![0u8; RECOMMENDED_SALT_LEN];
115   let mut username_salt = vec![0u8; RECOMMENDED_SALT_LEN];
116
117   OsRng
118      .try_fill_bytes(&mut password_salt)
119      .map_err(|e| Error::Custom(e.to_string()))?;
120   OsRng
121      .try_fill_bytes(&mut username_salt)
122      .map_err(|e| Error::Custom(e.to_string()))?;
123
124   let cipher_nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
125
126   let mut aad = credentials
127      .username
128      .unlock_str(|username_str| argon2.hash_password(&username_str, username_salt.clone()))
129      .map_err(|e| Error::Custom(e.to_string()))?;
130
131   let password_hash = credentials
132      .password
133      .unlock_str(|password_str| argon2.hash_password(&password_str, password_salt.clone()))
134      .map_err(|e| Error::Custom(e.to_string()))?;
135
136   let payload = Payload {
137      msg: data,
138      aad: &aad,
139   };
140
141   let cipher = xchacha20_poly_1305(password_hash);
142
143   let encrypted_data_res = cipher.encrypt(&cipher_nonce, payload);
144   aad.zeroize();
145
146   let encrypted_data = match encrypted_data_res {
147      Ok(data) => data,
148      Err(e) => {
149         return Err(Error::EncryptionFailed(e.to_string()));
150      }
151   };
152
153   let info = EncryptedInfo::new(
154      password_salt,
155      username_salt,
156      cipher_nonce.to_vec(),
157      argon2,
158   );
159
160   Ok((encrypted_data, info))
161}
162
163pub(crate) fn xchacha20_poly_1305(mut hash_output: Vec<u8>) -> XChaCha20Poly1305 {
164   let mut key = GenericArray::clone_from_slice(&hash_output[..32]);
165   hash_output.zeroize();
166
167   let cipher = XChaCha20Poly1305::new(&key);
168   key.zeroize();
169   cipher
170}