1use super::*;
2
3use bincode::{config::standard, encode_to_vec};
4use chacha20poly1305::{
5 AeadCore, KeyInit, XChaCha20Poly1305,
6 aead::{Aead, OsRng, Payload, generic_array::GenericArray, rand_core::RngCore},
7};
8use secure_types::SecureBytes;
9
10pub const HEADER: &[u8; 8] = b"nCrypt1\0";
26
27pub fn encrypt_data(
35 argon2: Argon2,
36 data: SecureBytes,
37 credentials: Credentials,
38) -> Result<Vec<u8>, Error> {
39 let (encrypted_data, info) = encrypt(argon2, credentials, data)?;
40
41 let encoded_info =
42 encode_to_vec(&info, standard()).map_err(|e| Error::EncodingFailed(e.to_string()))?;
43
44 let mut result = Vec::new();
46
47 result.extend_from_slice(HEADER);
49
50 let info_length = encoded_info.len() as u32;
52 result.extend_from_slice(&info_length.to_le_bytes());
53
54 result.extend_from_slice(&encoded_info);
56
57 result.extend_from_slice(&encrypted_data);
59
60 Ok(result)
61}
62
63fn encrypt(
64 argon2: Argon2,
65 credentials: Credentials,
66 data: SecureBytes,
67) -> Result<(Vec<u8>, EncryptedInfo), Error> {
68 credentials.is_valid()?;
69
70 if argon2.hash_length < 32 {
71 return Err(Error::HashLength);
72 }
73
74 let mut password_salt = vec![0u8; RECOMMENDED_SALT_LEN];
75 let mut username_salt = vec![0u8; RECOMMENDED_SALT_LEN];
76
77 OsRng
78 .try_fill_bytes(&mut password_salt)
79 .map_err(|e| Error::Custom(e.to_string()))?;
80 OsRng
81 .try_fill_bytes(&mut username_salt)
82 .map_err(|e| Error::Custom(e.to_string()))?;
83
84 let cipher_nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
85
86 let password_hash = argon2.hash_password(&credentials.password, password_salt.clone())?;
87 let username_hash = argon2.hash_password(&credentials.username, username_salt.clone())?;
88
89 data.slice_scope(|data| {
90 let mut aad = username_hash.slice_scope(|bytes| bytes.to_vec());
91
92 let payload = Payload {
93 msg: data,
94 aad: &aad,
95 };
96
97 let cipher = xchacha20_poly_1305(password_hash);
98
99 let encrypted_data_res = cipher.encrypt(&cipher_nonce, payload);
100 aad.zeroize();
101
102 let encrypted_data = match encrypted_data_res {
103 Ok(data) => data,
104 Err(e) => {
105 return Err(Error::EncryptionFailed(e.to_string()));
106 }
107 };
108
109 let info = EncryptedInfo::new(
110 password_salt,
111 username_salt,
112 cipher_nonce.to_vec(),
113 argon2,
114 );
115
116 Ok((encrypted_data, info))
117 })
118}
119
120pub(crate) fn xchacha20_poly_1305(hash_output: SecureBytes) -> XChaCha20Poly1305 {
121 let mut key = hash_output.slice_scope(|bytes| *GenericArray::from_slice(&bytes[..32]));
122
123 let cipher = XChaCha20Poly1305::new(&key);
124 key.zeroize();
125 cipher
126}