dcrypt_algorithms/aead/chacha20poly1305/
mod.rs1#[cfg(feature = "alloc")]
17use crate::alloc_prelude::*;
18
19use crate::error::{validate, Error, Result};
20use crate::mac::poly1305::{Poly1305, POLY1305_KEY_SIZE, POLY1305_TAG_SIZE};
21use crate::stream::chacha::chacha20::{ChaCha20, CHACHA20_KEY_SIZE, CHACHA20_NONCE_SIZE};
22use crate::types::nonce::ChaCha20Compatible;
23use crate::types::Nonce;
24use crate::types::SecretBytes;
25use crate::types::Tag;
26use dcrypt_api::error::Error as CoreError;
27use dcrypt_api::traits::symmetric::{DecryptOperation, EncryptOperation, Operation};
28use dcrypt_api::traits::{AuthenticatedCipher, SymmetricCipher};
29use dcrypt_api::types::Ciphertext;
30use dcrypt_common::security::SecretBuffer;
32use dcrypt_internal::constant_time::ConstantTimeEq;
33use dcrypt_internal::random::{try_fill_bytes_zeroing_on_error, CryptoRng, RngCore};
34use dcrypt_internal::zeroing::{
35 boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing, ZeroizingBytes,
36};
37
38pub const CHACHA20POLY1305_KEY_SIZE: usize = CHACHA20_KEY_SIZE;
40pub const CHACHA20POLY1305_NONCE_SIZE: usize = CHACHA20_NONCE_SIZE;
42pub const CHACHA20POLY1305_TAG_SIZE: usize = POLY1305_TAG_SIZE;
44
45fn core_random_error(_: dcrypt_internal::random::Error) -> CoreError {
46 CoreError::Other {
47 context: "randomness",
48 #[cfg(feature = "std")]
49 message: "caller-provided randomness source failed".to_string(),
50 }
51}
52const CHACHA20POLY1305_MAX_DATA_BYTES: u128 = (u32::MAX as u128) * 64;
53
54fn validate_data_length(data_len: usize) -> Result<()> {
55 validate::parameter(
56 (data_len as u128) <= CHACHA20POLY1305_MAX_DATA_BYTES,
57 "message_length",
58 "ChaCha20Poly1305 message would wrap the block counter",
59 )
60}
61
62#[derive(Clone)]
64pub struct ChaCha20Poly1305 {
65 key: SecretBuffer<CHACHA20POLY1305_KEY_SIZE>,
66}
67
68impl Zeroize for ChaCha20Poly1305 {
69 fn zeroize(&mut self) {
70 self.key.zeroize();
71 }
72}
73
74impl Drop for ChaCha20Poly1305 {
75 fn drop(&mut self) {
76 self.zeroize();
77 }
78}
79
80impl ZeroizeOnDrop for ChaCha20Poly1305 {}
81
82pub struct ChaCha20Poly1305EncryptOperation<'a> {
84 cipher: &'a ChaCha20Poly1305,
85 nonce: Option<&'a Nonce<CHACHA20POLY1305_NONCE_SIZE>>,
86 aad: Option<&'a [u8]>,
87}
88
89pub struct ChaCha20Poly1305DecryptOperation<'a> {
91 cipher: &'a ChaCha20Poly1305,
92 nonce: Option<&'a Nonce<CHACHA20POLY1305_NONCE_SIZE>>,
93 aad: Option<&'a [u8]>,
94}
95
96impl ChaCha20Poly1305 {
97 pub fn new(key: &[u8; CHACHA20POLY1305_KEY_SIZE]) -> Self {
99 Self {
100 key: SecretBuffer::new(*key),
101 }
102 }
103
104 fn poly1305_key(
106 &self,
107 nonce: &[u8; CHACHA20POLY1305_NONCE_SIZE],
108 ) -> Zeroizing<[u8; POLY1305_KEY_SIZE]> {
109 let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::from_slice(nonce).expect("Valid nonce"); let key_array: &[u8; CHACHA20_KEY_SIZE] = self
114 .key
115 .as_ref()
116 .try_into()
117 .expect("SecretBuffer has correct size");
118
119 let mut chacha = ChaCha20::new(key_array, &nonce_obj);
120 let mut poly_key = Zeroizing::new([0u8; POLY1305_KEY_SIZE]);
121 chacha
123 .keystream(&mut poly_key[..])
124 .expect("fresh ChaCha20 counter has capacity for one block");
125 poly_key
126 }
127
128 pub fn encrypt_with_nonce(
145 &self,
146 nonce: &[u8; CHACHA20POLY1305_NONCE_SIZE],
147 plaintext: &[u8],
148 aad: Option<&[u8]>,
149 ) -> Result<Vec<u8>> {
150 validate_data_length(plaintext.len())?;
153 let output_len =
154 plaintext
155 .len()
156 .checked_add(POLY1305_TAG_SIZE)
157 .ok_or(Error::Processing {
158 operation: "ChaCha20Poly1305 encryption",
159 details: "ciphertext length overflow",
160 })?;
161 let poly_key = self.poly1305_key(nonce);
162
163 let mut ct_buf = Zeroizing::new(boxed_bytes_zeroed(output_len));
165
166 ct_buf[..plaintext.len()].copy_from_slice(plaintext);
168
169 let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::from_slice(nonce)
171 .map_err(|_| Error::param("nonce", "Failed to create nonce from slice"))?;
172
173 let key_array: &[u8; CHACHA20_KEY_SIZE] = self
175 .key
176 .as_ref()
177 .try_into()
178 .expect("SecretBuffer has correct size");
179
180 ChaCha20::with_counter(key_array, &nonce_obj, 1).encrypt(&mut ct_buf[..plaintext.len()])?;
181
182 let tag = self.calculate_tag_ct(&poly_key, aad, &ct_buf[..plaintext.len()])?;
184 ct_buf[plaintext.len()..].copy_from_slice(tag.as_ref());
185 Ok(ct_buf.into_inner().into_vec())
186 }
187
188 pub fn decrypt_with_nonce(
208 &self,
209 nonce: &[u8; CHACHA20POLY1305_NONCE_SIZE],
210 ciphertext: &[u8],
211 aad: Option<&[u8]>,
212 ) -> Result<Vec<u8>> {
213 Ok(self
214 .decrypt_with_nonce_protected(nonce, ciphertext, aad)?
215 .into_inner()
216 .into_vec())
217 }
218
219 pub fn decrypt_with_nonce_protected(
223 &self,
224 nonce: &[u8; CHACHA20POLY1305_NONCE_SIZE],
225 ciphertext: &[u8],
226 aad: Option<&[u8]>,
227 ) -> Result<ZeroizingBytes> {
228 validate::min_length(
230 "ChaCha20Poly1305 ciphertext",
231 ciphertext.len(),
232 POLY1305_TAG_SIZE,
233 )?;
234
235 let ct_len = ciphertext.len() - POLY1305_TAG_SIZE;
236 let (encrypted, tag) = ciphertext.split_at(ct_len);
237 validate_data_length(encrypted.len())?;
238
239 let poly_key = self.poly1305_key(nonce);
241 let expected = self.calculate_tag_ct(&poly_key, aad, encrypted)?;
242 let tag_ok = expected.as_ref().ct_eq(tag);
243
244 let mut m = Zeroizing::new(boxed_bytes_zeroed(encrypted.len()));
246 m.copy_from_slice(encrypted);
247
248 let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::from_slice(nonce)
250 .map_err(|_| Error::param("nonce", "Failed to create nonce from slice"))?;
251
252 let key_array: &[u8; CHACHA20_KEY_SIZE] = self
254 .key
255 .as_ref()
256 .try_into()
257 .expect("SecretBuffer has correct size");
258
259 ChaCha20::with_counter(key_array, &nonce_obj, 1).decrypt(&mut m)?;
260
261 let mask = 0u8.wrapping_sub(tag_ok.unwrap_u8());
264
265 for byte in m.iter_mut() {
267 *byte &= mask;
268 }
269
270 let mut burn = m.clone();
273 burn.fill(0); drop(burn);
275
276 if bool::from(tag_ok) {
277 Ok(m)
278 } else {
279 Err(Error::Authentication {
280 algorithm: "ChaCha20Poly1305",
281 }) }
283 }
284
285 fn calculate_tag_ct(
291 &self,
292 poly_key: &[u8; POLY1305_KEY_SIZE],
293 aad: Option<&[u8]>,
294 ciphertext: &[u8],
295 ) -> Result<Tag<POLY1305_TAG_SIZE>> {
296 let mut poly = Poly1305::new(poly_key)?;
297 let aad_slice = aad.unwrap_or(&[]);
298
299 const ZERO16: [u8; 16] = [0u8; 16];
300
301 poly.update(aad_slice)?;
303 poly.update(&ZERO16[..(16 - aad_slice.len() % 16) % 16])?;
304
305 poly.update(ciphertext)?;
307 poly.update(&ZERO16[..(16 - ciphertext.len() % 16) % 16])?;
308
309 let mut len_block = [0u8; 16];
311 len_block[..8].copy_from_slice(&(aad_slice.len() as u64).to_le_bytes());
312 len_block[8..].copy_from_slice(&(ciphertext.len() as u64).to_le_bytes());
313 poly.update(&len_block)?;
314
315 let tag = poly.finalize();
317 Ok(tag)
318 }
319
320 pub fn encrypt<const N: usize>(
322 &self,
323 nonce: &Nonce<N>,
324 plaintext: &[u8],
325 aad: Option<&[u8]>,
326 ) -> Result<Vec<u8>>
327 where
328 Nonce<N>: ChaCha20Compatible,
329 {
330 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
331 nonce_array.copy_from_slice(nonce.as_ref());
332 self.encrypt_with_nonce(&nonce_array, plaintext, aad)
333 }
334
335 pub fn decrypt<const N: usize>(
337 &self,
338 nonce: &Nonce<N>,
339 ciphertext: &[u8],
340 aad: Option<&[u8]>,
341 ) -> Result<Vec<u8>>
342 where
343 Nonce<N>: ChaCha20Compatible,
344 {
345 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
346 nonce_array.copy_from_slice(nonce.as_ref());
347 self.decrypt_with_nonce(&nonce_array, ciphertext, aad)
348 }
349}
350
351impl AuthenticatedCipher for ChaCha20Poly1305 {
353 const TAG_SIZE: usize = POLY1305_TAG_SIZE;
354 const ALGORITHM_ID: &'static str = "ChaCha20Poly1305";
355}
356
357impl SymmetricCipher for ChaCha20Poly1305 {
359 type Key = SecretBytes<CHACHA20POLY1305_KEY_SIZE>;
360 type Nonce = Nonce<CHACHA20POLY1305_NONCE_SIZE>;
361 type Ciphertext = Ciphertext;
362 type EncryptOperation<'a>
363 = ChaCha20Poly1305EncryptOperation<'a>
364 where
365 Self: 'a;
366 type DecryptOperation<'a>
367 = ChaCha20Poly1305DecryptOperation<'a>
368 where
369 Self: 'a;
370
371 fn name() -> &'static str {
372 "ChaCha20Poly1305"
373 }
374
375 fn encrypt(&self) -> Self::EncryptOperation<'_> {
376 ChaCha20Poly1305EncryptOperation {
377 cipher: self,
378 nonce: None,
379 aad: None,
380 }
381 }
382
383 fn decrypt(&self) -> Self::DecryptOperation<'_> {
384 ChaCha20Poly1305DecryptOperation {
385 cipher: self,
386 nonce: None,
387 aad: None,
388 }
389 }
390
391 fn generate_key<R: RngCore + CryptoRng>(
392 rng: &mut R,
393 ) -> core::result::Result<Self::Key, CoreError> {
394 let mut key_data = Zeroizing::new([0u8; CHACHA20POLY1305_KEY_SIZE]);
395 try_fill_bytes_zeroing_on_error(rng, &mut key_data[..]).map_err(core_random_error)?;
396 Ok(SecretBytes::new(*key_data))
397 }
398
399 fn generate_nonce<R: RngCore + CryptoRng>(
400 rng: &mut R,
401 ) -> core::result::Result<Self::Nonce, CoreError> {
402 let mut nonce_data = [0u8; CHACHA20POLY1305_NONCE_SIZE];
403 try_fill_bytes_zeroing_on_error(rng, &mut nonce_data).map_err(core_random_error)?;
404 Ok(Nonce::new(nonce_data))
405 }
406
407 fn derive_key_from_bytes(bytes: &[u8]) -> core::result::Result<Self::Key, CoreError> {
408 if bytes.len() < CHACHA20POLY1305_KEY_SIZE {
409 return Err(CoreError::InvalidLength {
410 context: "ChaCha20Poly1305 key derivation",
411 expected: CHACHA20POLY1305_KEY_SIZE,
412 actual: bytes.len(),
413 });
414 }
415
416 let mut key_data = Zeroizing::new([0u8; CHACHA20POLY1305_KEY_SIZE]);
417 key_data.copy_from_slice(&bytes[..CHACHA20POLY1305_KEY_SIZE]);
418 Ok(SecretBytes::new(key_data.into_inner()))
419 }
420}
421
422impl Operation<Ciphertext> for ChaCha20Poly1305EncryptOperation<'_> {
424 fn execute(self) -> core::result::Result<Ciphertext, CoreError> {
425 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
426 context: "ChaCha20Poly1305 encryption",
427 #[cfg(feature = "std")]
428 message: "Nonce is required for ChaCha20Poly1305 encryption".to_string(),
429 })?;
430
431 let plaintext = b""; let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
434 nonce_array.copy_from_slice(nonce.as_ref());
435
436 let ciphertext = self
437 .cipher
438 .encrypt_with_nonce(&nonce_array, plaintext, self.aad)
439 .map_err(CoreError::from)?;
440
441 Ok(Ciphertext::new(ciphertext))
442 }
443}
444
445impl<'a> EncryptOperation<'a, ChaCha20Poly1305> for ChaCha20Poly1305EncryptOperation<'a> {
446 fn with_nonce(mut self, nonce: &'a <ChaCha20Poly1305 as SymmetricCipher>::Nonce) -> Self {
447 self.nonce = Some(nonce);
448 self
449 }
450
451 fn with_aad(mut self, aad: &'a [u8]) -> Self {
452 self.aad = Some(aad);
453 self
454 }
455
456 fn encrypt(self, plaintext: &'a [u8]) -> core::result::Result<Ciphertext, CoreError> {
457 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
458 context: "ChaCha20Poly1305 encryption",
459 #[cfg(feature = "std")]
460 message: "Nonce is required for ChaCha20Poly1305 encryption".to_string(),
461 })?;
462
463 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
464 nonce_array.copy_from_slice(nonce.as_ref());
465
466 let ciphertext = self
467 .cipher
468 .encrypt_with_nonce(&nonce_array, plaintext, self.aad)
469 .map_err(CoreError::from)?;
470
471 Ok(Ciphertext::new(ciphertext))
472 }
473}
474
475impl Operation<Vec<u8>> for ChaCha20Poly1305DecryptOperation<'_> {
477 fn execute(self) -> core::result::Result<Vec<u8>, CoreError> {
478 Err(CoreError::InvalidParameter {
479 context: "ChaCha20Poly1305 decryption",
480 #[cfg(feature = "std")]
481 message: "Use decrypt method instead".to_string(),
482 })
483 }
484}
485
486impl<'a> DecryptOperation<'a, ChaCha20Poly1305> for ChaCha20Poly1305DecryptOperation<'a> {
487 fn with_nonce(mut self, nonce: &'a <ChaCha20Poly1305 as SymmetricCipher>::Nonce) -> Self {
488 self.nonce = Some(nonce);
489 self
490 }
491
492 fn with_aad(mut self, aad: &'a [u8]) -> Self {
493 self.aad = Some(aad);
494 self
495 }
496
497 fn decrypt(
498 self,
499 ciphertext: &'a <ChaCha20Poly1305 as SymmetricCipher>::Ciphertext,
500 ) -> core::result::Result<Vec<u8>, CoreError> {
501 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
502 context: "ChaCha20Poly1305 decryption",
503 #[cfg(feature = "std")]
504 message: "Nonce is required for ChaCha20Poly1305 decryption".to_string(),
505 })?;
506
507 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
508 nonce_array.copy_from_slice(nonce.as_ref());
509
510 self.cipher
511 .decrypt_with_nonce(&nonce_array, ciphertext.as_ref(), self.aad)
512 .map_err(CoreError::from)
513 }
514}
515
516#[cfg(test)]
517mod tests;