dcrypt_algorithms/aead/chacha20poly1305/
mod.rs1use crate::error::{validate, Error, Result};
14use crate::mac::poly1305::{Poly1305, POLY1305_KEY_SIZE, POLY1305_TAG_SIZE};
15use crate::stream::chacha::chacha20::{ChaCha20, CHACHA20_KEY_SIZE, CHACHA20_NONCE_SIZE};
16use crate::types::nonce::ChaCha20Compatible;
17use crate::types::Nonce;
18use crate::types::SecretBytes;
19use crate::types::Tag;
20use dcrypt_api::error::Error as CoreError;
21use dcrypt_api::traits::symmetric::{DecryptOperation, EncryptOperation, Operation};
22use dcrypt_api::traits::{AuthenticatedCipher, SymmetricCipher};
23use dcrypt_api::types::Ciphertext;
24use dcrypt_common::security::SecretBuffer;
26use subtle::ConstantTimeEq;
27use zeroize::{Zeroize, ZeroizeOnDrop};
28
29pub const CHACHA20POLY1305_KEY_SIZE: usize = CHACHA20_KEY_SIZE;
31pub const CHACHA20POLY1305_NONCE_SIZE: usize = CHACHA20_NONCE_SIZE;
33pub const CHACHA20POLY1305_TAG_SIZE: usize = POLY1305_TAG_SIZE;
35
36#[derive(Clone, Zeroize, ZeroizeOnDrop)]
38pub struct ChaCha20Poly1305 {
39 key: SecretBuffer<CHACHA20POLY1305_KEY_SIZE>,
40}
41
42pub struct ChaCha20Poly1305EncryptOperation<'a> {
44 cipher: &'a ChaCha20Poly1305,
45 nonce: Option<&'a Nonce<CHACHA20POLY1305_NONCE_SIZE>>,
46 aad: Option<&'a [u8]>,
47}
48
49pub struct ChaCha20Poly1305DecryptOperation<'a> {
51 cipher: &'a ChaCha20Poly1305,
52 nonce: Option<&'a Nonce<CHACHA20POLY1305_NONCE_SIZE>>,
53 aad: Option<&'a [u8]>,
54}
55
56impl ChaCha20Poly1305 {
57 pub fn new(key: &[u8; CHACHA20POLY1305_KEY_SIZE]) -> Self {
59 Self {
60 key: SecretBuffer::new(*key),
61 }
62 }
63
64 fn poly1305_key(&self, nonce: &[u8; CHACHA20POLY1305_NONCE_SIZE]) -> [u8; POLY1305_KEY_SIZE] {
66 let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::from_slice(nonce).expect("Valid nonce"); let key_array: &[u8; CHACHA20_KEY_SIZE] = self
71 .key
72 .as_ref()
73 .try_into()
74 .expect("SecretBuffer has correct size");
75
76 let mut chacha = ChaCha20::new(key_array, &nonce_obj);
77 let mut poly_key = [0u8; POLY1305_KEY_SIZE];
78 chacha.keystream(&mut poly_key);
79 poly_key
80 }
81
82 pub fn encrypt_with_nonce(
99 &self,
100 nonce: &[u8; CHACHA20POLY1305_NONCE_SIZE],
101 plaintext: &[u8],
102 aad: Option<&[u8]>,
103 ) -> Result<Vec<u8>> {
104 let poly_key = self.poly1305_key(nonce);
105
106 let mut ct_buf = Vec::with_capacity(plaintext.len() + POLY1305_TAG_SIZE);
108
109 ct_buf.extend_from_slice(plaintext);
111
112 let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::from_slice(nonce)
114 .map_err(|_| Error::param("nonce", "Failed to create nonce from slice"))?;
115
116 let key_array: &[u8; CHACHA20_KEY_SIZE] = self
118 .key
119 .as_ref()
120 .try_into()
121 .expect("SecretBuffer has correct size");
122
123 ChaCha20::with_counter(key_array, &nonce_obj, 1).encrypt(&mut ct_buf);
124
125 let tag = self.calculate_tag_ct(&poly_key, aad, &ct_buf)?;
127 ct_buf.extend_from_slice(tag.as_ref());
128 Ok(ct_buf)
129 }
130
131 pub fn decrypt_with_nonce(
151 &self,
152 nonce: &[u8; CHACHA20POLY1305_NONCE_SIZE],
153 ciphertext: &[u8],
154 aad: Option<&[u8]>,
155 ) -> Result<Vec<u8>> {
156 validate::min_length(
158 "ChaCha20Poly1305 ciphertext",
159 ciphertext.len(),
160 POLY1305_TAG_SIZE,
161 )?;
162
163 let ct_len = ciphertext.len() - POLY1305_TAG_SIZE;
164 let (encrypted, tag) = ciphertext.split_at(ct_len);
165
166 let poly_key = self.poly1305_key(nonce);
168 let expected = self.calculate_tag_ct(&poly_key, aad, encrypted)?;
169 let tag_ok = expected.as_ref().ct_eq(tag); let mut m = Vec::with_capacity(encrypted.len());
173 m.extend_from_slice(encrypted);
174
175 let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::from_slice(nonce)
177 .map_err(|_| Error::param("nonce", "Failed to create nonce from slice"))?;
178
179 let key_array: &[u8; CHACHA20_KEY_SIZE] = self
181 .key
182 .as_ref()
183 .try_into()
184 .expect("SecretBuffer has correct size");
185
186 ChaCha20::with_counter(key_array, &nonce_obj, 1).decrypt(&mut m);
187
188 let mask = 0u8.wrapping_sub(tag_ok.unwrap_u8());
191
192 for byte in &mut m {
194 *byte &= mask;
195 }
196
197 let mut burn = m.clone();
200 burn.fill(0); drop(burn);
202
203 if bool::from(tag_ok) {
204 Ok(m) } else {
206 Err(Error::Authentication {
207 algorithm: "ChaCha20Poly1305",
208 }) }
210 }
211
212 fn calculate_tag_ct(
218 &self,
219 poly_key: &[u8; POLY1305_KEY_SIZE],
220 aad: Option<&[u8]>,
221 ciphertext: &[u8],
222 ) -> Result<Tag<POLY1305_TAG_SIZE>> {
223 let mut poly = Poly1305::new(poly_key)?;
224 let aad_slice = aad.unwrap_or(&[]);
225
226 const ZERO16: [u8; 16] = [0u8; 16];
227
228 poly.update(aad_slice)?;
230 poly.update(&ZERO16[..(16 - aad_slice.len() % 16) % 16])?;
231
232 poly.update(ciphertext)?;
234 poly.update(&ZERO16[..(16 - ciphertext.len() % 16) % 16])?;
235
236 let mut len_block = [0u8; 16];
238 len_block[..8].copy_from_slice(&(aad_slice.len() as u64).to_le_bytes());
239 len_block[8..].copy_from_slice(&(ciphertext.len() as u64).to_le_bytes());
240 poly.update(&len_block)?;
241
242 let tag = poly.finalize();
244 Ok(tag)
245 }
246
247 pub fn encrypt<const N: usize>(
249 &self,
250 nonce: &Nonce<N>,
251 plaintext: &[u8],
252 aad: Option<&[u8]>,
253 ) -> Result<Vec<u8>>
254 where
255 Nonce<N>: ChaCha20Compatible,
256 {
257 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
258 nonce_array.copy_from_slice(nonce.as_ref());
259 self.encrypt_with_nonce(&nonce_array, plaintext, aad)
260 }
261
262 pub fn decrypt<const N: usize>(
264 &self,
265 nonce: &Nonce<N>,
266 ciphertext: &[u8],
267 aad: Option<&[u8]>,
268 ) -> Result<Vec<u8>>
269 where
270 Nonce<N>: ChaCha20Compatible,
271 {
272 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
273 nonce_array.copy_from_slice(nonce.as_ref());
274 self.decrypt_with_nonce(&nonce_array, ciphertext, aad)
275 }
276}
277
278impl AuthenticatedCipher for ChaCha20Poly1305 {
280 const TAG_SIZE: usize = POLY1305_TAG_SIZE;
281 const ALGORITHM_ID: &'static str = "ChaCha20Poly1305";
282}
283
284impl SymmetricCipher for ChaCha20Poly1305 {
286 type Key = SecretBytes<CHACHA20POLY1305_KEY_SIZE>;
287 type Nonce = Nonce<CHACHA20POLY1305_NONCE_SIZE>;
288 type Ciphertext = Ciphertext;
289 type EncryptOperation<'a>
290 = ChaCha20Poly1305EncryptOperation<'a>
291 where
292 Self: 'a;
293 type DecryptOperation<'a>
294 = ChaCha20Poly1305DecryptOperation<'a>
295 where
296 Self: 'a;
297
298 fn name() -> &'static str {
299 "ChaCha20Poly1305"
300 }
301
302 fn encrypt(&self) -> Self::EncryptOperation<'_> {
303 ChaCha20Poly1305EncryptOperation {
304 cipher: self,
305 nonce: None,
306 aad: None,
307 }
308 }
309
310 fn decrypt(&self) -> Self::DecryptOperation<'_> {
311 ChaCha20Poly1305DecryptOperation {
312 cipher: self,
313 nonce: None,
314 aad: None,
315 }
316 }
317
318 fn generate_key<R: rand::RngCore + rand::CryptoRng>(
319 rng: &mut R,
320 ) -> std::result::Result<Self::Key, CoreError> {
321 let mut key_data = [0u8; CHACHA20POLY1305_KEY_SIZE];
322 rng.fill_bytes(&mut key_data);
323 Ok(SecretBytes::new(key_data))
324 }
325
326 fn generate_nonce<R: rand::RngCore + rand::CryptoRng>(
327 rng: &mut R,
328 ) -> std::result::Result<Self::Nonce, CoreError> {
329 let mut nonce_data = [0u8; CHACHA20POLY1305_NONCE_SIZE];
330 rng.fill_bytes(&mut nonce_data);
331 Ok(Nonce::new(nonce_data))
332 }
333
334 fn derive_key_from_bytes(bytes: &[u8]) -> std::result::Result<Self::Key, CoreError> {
335 if bytes.len() < CHACHA20POLY1305_KEY_SIZE {
336 return Err(CoreError::InvalidLength {
337 context: "ChaCha20Poly1305 key derivation",
338 expected: CHACHA20POLY1305_KEY_SIZE,
339 actual: bytes.len(),
340 });
341 }
342
343 let mut key_data = [0u8; CHACHA20POLY1305_KEY_SIZE];
344 key_data.copy_from_slice(&bytes[..CHACHA20POLY1305_KEY_SIZE]);
345 Ok(SecretBytes::new(key_data))
346 }
347}
348
349impl Operation<Ciphertext> for ChaCha20Poly1305EncryptOperation<'_> {
351 fn execute(self) -> std::result::Result<Ciphertext, CoreError> {
352 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
353 context: "ChaCha20Poly1305 encryption",
354 #[cfg(feature = "std")]
355 message: "Nonce is required for ChaCha20Poly1305 encryption".to_string(),
356 })?;
357
358 let plaintext = b""; let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
361 nonce_array.copy_from_slice(nonce.as_ref());
362
363 let ciphertext = self
364 .cipher
365 .encrypt_with_nonce(&nonce_array, plaintext, self.aad)
366 .map_err(CoreError::from)?;
367
368 Ok(Ciphertext::new(&ciphertext))
369 }
370}
371
372impl<'a> EncryptOperation<'a, ChaCha20Poly1305> for ChaCha20Poly1305EncryptOperation<'a> {
373 fn with_nonce(mut self, nonce: &'a <ChaCha20Poly1305 as SymmetricCipher>::Nonce) -> Self {
374 self.nonce = Some(nonce);
375 self
376 }
377
378 fn with_aad(mut self, aad: &'a [u8]) -> Self {
379 self.aad = Some(aad);
380 self
381 }
382
383 fn encrypt(self, plaintext: &'a [u8]) -> std::result::Result<Ciphertext, CoreError> {
384 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
385 context: "ChaCha20Poly1305 encryption",
386 #[cfg(feature = "std")]
387 message: "Nonce is required for ChaCha20Poly1305 encryption".to_string(),
388 })?;
389
390 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
391 nonce_array.copy_from_slice(nonce.as_ref());
392
393 let ciphertext = self
394 .cipher
395 .encrypt_with_nonce(&nonce_array, plaintext, self.aad)
396 .map_err(CoreError::from)?;
397
398 Ok(Ciphertext::new(&ciphertext))
399 }
400}
401
402impl Operation<Vec<u8>> for ChaCha20Poly1305DecryptOperation<'_> {
404 fn execute(self) -> std::result::Result<Vec<u8>, CoreError> {
405 Err(CoreError::InvalidParameter {
406 context: "ChaCha20Poly1305 decryption",
407 #[cfg(feature = "std")]
408 message: "Use decrypt method instead".to_string(),
409 })
410 }
411}
412
413impl<'a> DecryptOperation<'a, ChaCha20Poly1305> for ChaCha20Poly1305DecryptOperation<'a> {
414 fn with_nonce(mut self, nonce: &'a <ChaCha20Poly1305 as SymmetricCipher>::Nonce) -> Self {
415 self.nonce = Some(nonce);
416 self
417 }
418
419 fn with_aad(mut self, aad: &'a [u8]) -> Self {
420 self.aad = Some(aad);
421 self
422 }
423
424 fn decrypt(
425 self,
426 ciphertext: &'a <ChaCha20Poly1305 as SymmetricCipher>::Ciphertext,
427 ) -> std::result::Result<Vec<u8>, CoreError> {
428 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
429 context: "ChaCha20Poly1305 decryption",
430 #[cfg(feature = "std")]
431 message: "Nonce is required for ChaCha20Poly1305 decryption".to_string(),
432 })?;
433
434 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
435 nonce_array.copy_from_slice(nonce.as_ref());
436
437 self.cipher
438 .decrypt_with_nonce(&nonce_array, ciphertext.as_ref(), self.aad)
439 .map_err(CoreError::from)
440 }
441}
442
443#[cfg(test)]
444mod tests;