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;
35const CHACHA20POLY1305_MAX_DATA_BYTES: u128 = (u32::MAX as u128) * 64;
36
37fn validate_data_length(data_len: usize) -> Result<()> {
38 validate::parameter(
39 (data_len as u128) <= CHACHA20POLY1305_MAX_DATA_BYTES,
40 "message_length",
41 "ChaCha20Poly1305 message would wrap the block counter",
42 )
43}
44
45#[derive(Clone, Zeroize, ZeroizeOnDrop)]
47pub struct ChaCha20Poly1305 {
48 key: SecretBuffer<CHACHA20POLY1305_KEY_SIZE>,
49}
50
51pub struct ChaCha20Poly1305EncryptOperation<'a> {
53 cipher: &'a ChaCha20Poly1305,
54 nonce: Option<&'a Nonce<CHACHA20POLY1305_NONCE_SIZE>>,
55 aad: Option<&'a [u8]>,
56}
57
58pub struct ChaCha20Poly1305DecryptOperation<'a> {
60 cipher: &'a ChaCha20Poly1305,
61 nonce: Option<&'a Nonce<CHACHA20POLY1305_NONCE_SIZE>>,
62 aad: Option<&'a [u8]>,
63}
64
65impl ChaCha20Poly1305 {
66 pub fn new(key: &[u8; CHACHA20POLY1305_KEY_SIZE]) -> Self {
68 Self {
69 key: SecretBuffer::new(*key),
70 }
71 }
72
73 fn poly1305_key(&self, nonce: &[u8; CHACHA20POLY1305_NONCE_SIZE]) -> [u8; POLY1305_KEY_SIZE] {
75 let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::from_slice(nonce).expect("Valid nonce"); let key_array: &[u8; CHACHA20_KEY_SIZE] = self
80 .key
81 .as_ref()
82 .try_into()
83 .expect("SecretBuffer has correct size");
84
85 let mut chacha = ChaCha20::new(key_array, &nonce_obj);
86 let mut poly_key = [0u8; POLY1305_KEY_SIZE];
87 chacha
89 .keystream(&mut poly_key)
90 .expect("fresh ChaCha20 counter has capacity for one block");
91 poly_key
92 }
93
94 pub fn encrypt_with_nonce(
111 &self,
112 nonce: &[u8; CHACHA20POLY1305_NONCE_SIZE],
113 plaintext: &[u8],
114 aad: Option<&[u8]>,
115 ) -> Result<Vec<u8>> {
116 validate_data_length(plaintext.len())?;
119 let output_len =
120 plaintext
121 .len()
122 .checked_add(POLY1305_TAG_SIZE)
123 .ok_or(Error::Processing {
124 operation: "ChaCha20Poly1305 encryption",
125 details: "ciphertext length overflow",
126 })?;
127 let poly_key = self.poly1305_key(nonce);
128
129 let mut ct_buf = Vec::with_capacity(output_len);
131
132 ct_buf.extend_from_slice(plaintext);
134
135 let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::from_slice(nonce)
137 .map_err(|_| Error::param("nonce", "Failed to create nonce from slice"))?;
138
139 let key_array: &[u8; CHACHA20_KEY_SIZE] = self
141 .key
142 .as_ref()
143 .try_into()
144 .expect("SecretBuffer has correct size");
145
146 ChaCha20::with_counter(key_array, &nonce_obj, 1).encrypt(&mut ct_buf)?;
147
148 let tag = self.calculate_tag_ct(&poly_key, aad, &ct_buf)?;
150 ct_buf.extend_from_slice(tag.as_ref());
151 Ok(ct_buf)
152 }
153
154 pub fn decrypt_with_nonce(
174 &self,
175 nonce: &[u8; CHACHA20POLY1305_NONCE_SIZE],
176 ciphertext: &[u8],
177 aad: Option<&[u8]>,
178 ) -> Result<Vec<u8>> {
179 validate::min_length(
181 "ChaCha20Poly1305 ciphertext",
182 ciphertext.len(),
183 POLY1305_TAG_SIZE,
184 )?;
185
186 let ct_len = ciphertext.len() - POLY1305_TAG_SIZE;
187 let (encrypted, tag) = ciphertext.split_at(ct_len);
188 validate_data_length(encrypted.len())?;
189
190 let poly_key = self.poly1305_key(nonce);
192 let expected = self.calculate_tag_ct(&poly_key, aad, encrypted)?;
193 let tag_ok = expected.as_ref().ct_eq(tag); let mut m = Vec::with_capacity(encrypted.len());
197 m.extend_from_slice(encrypted);
198
199 let nonce_obj = Nonce::<CHACHA20_NONCE_SIZE>::from_slice(nonce)
201 .map_err(|_| Error::param("nonce", "Failed to create nonce from slice"))?;
202
203 let key_array: &[u8; CHACHA20_KEY_SIZE] = self
205 .key
206 .as_ref()
207 .try_into()
208 .expect("SecretBuffer has correct size");
209
210 ChaCha20::with_counter(key_array, &nonce_obj, 1).decrypt(&mut m)?;
211
212 let mask = 0u8.wrapping_sub(tag_ok.unwrap_u8());
215
216 for byte in &mut m {
218 *byte &= mask;
219 }
220
221 let mut burn = m.clone();
224 burn.fill(0); drop(burn);
226
227 if bool::from(tag_ok) {
228 Ok(m) } else {
230 Err(Error::Authentication {
231 algorithm: "ChaCha20Poly1305",
232 }) }
234 }
235
236 fn calculate_tag_ct(
242 &self,
243 poly_key: &[u8; POLY1305_KEY_SIZE],
244 aad: Option<&[u8]>,
245 ciphertext: &[u8],
246 ) -> Result<Tag<POLY1305_TAG_SIZE>> {
247 let mut poly = Poly1305::new(poly_key)?;
248 let aad_slice = aad.unwrap_or(&[]);
249
250 const ZERO16: [u8; 16] = [0u8; 16];
251
252 poly.update(aad_slice)?;
254 poly.update(&ZERO16[..(16 - aad_slice.len() % 16) % 16])?;
255
256 poly.update(ciphertext)?;
258 poly.update(&ZERO16[..(16 - ciphertext.len() % 16) % 16])?;
259
260 let mut len_block = [0u8; 16];
262 len_block[..8].copy_from_slice(&(aad_slice.len() as u64).to_le_bytes());
263 len_block[8..].copy_from_slice(&(ciphertext.len() as u64).to_le_bytes());
264 poly.update(&len_block)?;
265
266 let tag = poly.finalize();
268 Ok(tag)
269 }
270
271 pub fn encrypt<const N: usize>(
273 &self,
274 nonce: &Nonce<N>,
275 plaintext: &[u8],
276 aad: Option<&[u8]>,
277 ) -> Result<Vec<u8>>
278 where
279 Nonce<N>: ChaCha20Compatible,
280 {
281 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
282 nonce_array.copy_from_slice(nonce.as_ref());
283 self.encrypt_with_nonce(&nonce_array, plaintext, aad)
284 }
285
286 pub fn decrypt<const N: usize>(
288 &self,
289 nonce: &Nonce<N>,
290 ciphertext: &[u8],
291 aad: Option<&[u8]>,
292 ) -> Result<Vec<u8>>
293 where
294 Nonce<N>: ChaCha20Compatible,
295 {
296 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
297 nonce_array.copy_from_slice(nonce.as_ref());
298 self.decrypt_with_nonce(&nonce_array, ciphertext, aad)
299 }
300}
301
302impl AuthenticatedCipher for ChaCha20Poly1305 {
304 const TAG_SIZE: usize = POLY1305_TAG_SIZE;
305 const ALGORITHM_ID: &'static str = "ChaCha20Poly1305";
306}
307
308impl SymmetricCipher for ChaCha20Poly1305 {
310 type Key = SecretBytes<CHACHA20POLY1305_KEY_SIZE>;
311 type Nonce = Nonce<CHACHA20POLY1305_NONCE_SIZE>;
312 type Ciphertext = Ciphertext;
313 type EncryptOperation<'a>
314 = ChaCha20Poly1305EncryptOperation<'a>
315 where
316 Self: 'a;
317 type DecryptOperation<'a>
318 = ChaCha20Poly1305DecryptOperation<'a>
319 where
320 Self: 'a;
321
322 fn name() -> &'static str {
323 "ChaCha20Poly1305"
324 }
325
326 fn encrypt(&self) -> Self::EncryptOperation<'_> {
327 ChaCha20Poly1305EncryptOperation {
328 cipher: self,
329 nonce: None,
330 aad: None,
331 }
332 }
333
334 fn decrypt(&self) -> Self::DecryptOperation<'_> {
335 ChaCha20Poly1305DecryptOperation {
336 cipher: self,
337 nonce: None,
338 aad: None,
339 }
340 }
341
342 fn generate_key<R: rand::RngCore + rand::CryptoRng>(
343 rng: &mut R,
344 ) -> std::result::Result<Self::Key, CoreError> {
345 let mut key_data = [0u8; CHACHA20POLY1305_KEY_SIZE];
346 rng.fill_bytes(&mut key_data);
347 Ok(SecretBytes::new(key_data))
348 }
349
350 fn generate_nonce<R: rand::RngCore + rand::CryptoRng>(
351 rng: &mut R,
352 ) -> std::result::Result<Self::Nonce, CoreError> {
353 let mut nonce_data = [0u8; CHACHA20POLY1305_NONCE_SIZE];
354 rng.fill_bytes(&mut nonce_data);
355 Ok(Nonce::new(nonce_data))
356 }
357
358 fn derive_key_from_bytes(bytes: &[u8]) -> std::result::Result<Self::Key, CoreError> {
359 if bytes.len() < CHACHA20POLY1305_KEY_SIZE {
360 return Err(CoreError::InvalidLength {
361 context: "ChaCha20Poly1305 key derivation",
362 expected: CHACHA20POLY1305_KEY_SIZE,
363 actual: bytes.len(),
364 });
365 }
366
367 let mut key_data = [0u8; CHACHA20POLY1305_KEY_SIZE];
368 key_data.copy_from_slice(&bytes[..CHACHA20POLY1305_KEY_SIZE]);
369 Ok(SecretBytes::new(key_data))
370 }
371}
372
373impl Operation<Ciphertext> for ChaCha20Poly1305EncryptOperation<'_> {
375 fn execute(self) -> std::result::Result<Ciphertext, CoreError> {
376 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
377 context: "ChaCha20Poly1305 encryption",
378 #[cfg(feature = "std")]
379 message: "Nonce is required for ChaCha20Poly1305 encryption".to_string(),
380 })?;
381
382 let plaintext = b""; let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
385 nonce_array.copy_from_slice(nonce.as_ref());
386
387 let ciphertext = self
388 .cipher
389 .encrypt_with_nonce(&nonce_array, plaintext, self.aad)
390 .map_err(CoreError::from)?;
391
392 Ok(Ciphertext::new(ciphertext))
393 }
394}
395
396impl<'a> EncryptOperation<'a, ChaCha20Poly1305> for ChaCha20Poly1305EncryptOperation<'a> {
397 fn with_nonce(mut self, nonce: &'a <ChaCha20Poly1305 as SymmetricCipher>::Nonce) -> Self {
398 self.nonce = Some(nonce);
399 self
400 }
401
402 fn with_aad(mut self, aad: &'a [u8]) -> Self {
403 self.aad = Some(aad);
404 self
405 }
406
407 fn encrypt(self, plaintext: &'a [u8]) -> std::result::Result<Ciphertext, CoreError> {
408 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
409 context: "ChaCha20Poly1305 encryption",
410 #[cfg(feature = "std")]
411 message: "Nonce is required for ChaCha20Poly1305 encryption".to_string(),
412 })?;
413
414 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
415 nonce_array.copy_from_slice(nonce.as_ref());
416
417 let ciphertext = self
418 .cipher
419 .encrypt_with_nonce(&nonce_array, plaintext, self.aad)
420 .map_err(CoreError::from)?;
421
422 Ok(Ciphertext::new(ciphertext))
423 }
424}
425
426impl Operation<Vec<u8>> for ChaCha20Poly1305DecryptOperation<'_> {
428 fn execute(self) -> std::result::Result<Vec<u8>, CoreError> {
429 Err(CoreError::InvalidParameter {
430 context: "ChaCha20Poly1305 decryption",
431 #[cfg(feature = "std")]
432 message: "Use decrypt method instead".to_string(),
433 })
434 }
435}
436
437impl<'a> DecryptOperation<'a, ChaCha20Poly1305> for ChaCha20Poly1305DecryptOperation<'a> {
438 fn with_nonce(mut self, nonce: &'a <ChaCha20Poly1305 as SymmetricCipher>::Nonce) -> Self {
439 self.nonce = Some(nonce);
440 self
441 }
442
443 fn with_aad(mut self, aad: &'a [u8]) -> Self {
444 self.aad = Some(aad);
445 self
446 }
447
448 fn decrypt(
449 self,
450 ciphertext: &'a <ChaCha20Poly1305 as SymmetricCipher>::Ciphertext,
451 ) -> std::result::Result<Vec<u8>, CoreError> {
452 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
453 context: "ChaCha20Poly1305 decryption",
454 #[cfg(feature = "std")]
455 message: "Nonce is required for ChaCha20Poly1305 decryption".to_string(),
456 })?;
457
458 let mut nonce_array = [0u8; CHACHA20POLY1305_NONCE_SIZE];
459 nonce_array.copy_from_slice(nonce.as_ref());
460
461 self.cipher
462 .decrypt_with_nonce(&nonce_array, ciphertext.as_ref(), self.aad)
463 .map_err(CoreError::from)
464 }
465}
466
467#[cfg(test)]
468mod tests;