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;
8
9pub const HEADER: &[u8; 8] = b"nCrypt1\0";
25
26pub const HEADER_02: &[u8; 8] = b"nCrypt2\0";
27
28pub 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 let mut result = Vec::new();
47
48 result.extend_from_slice(HEADER_02);
50
51 let info_length = encoded_info.len() as u32;
53 result.extend_from_slice(&info_length.to_le_bytes());
54
55 result.extend_from_slice(&encoded_info);
57
58 result.extend_from_slice(&encrypted_data);
60
61 Ok(result)
62}
63
64pub 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 let mut result = Vec::new();
86
87 result.extend_from_slice(HEADER_02);
89
90 let info_length = encoded_info.len() as u32;
92 result.extend_from_slice(&info_length.to_le_bytes());
93
94 result.extend_from_slice(&encoded_info);
96
97 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}