1use chacha20::cipher::array::Array;
36use chacha20::cipher::consts::U64;
37use chacha20::cipher::{Block, KeyIvInit, StreamCipherCore};
38use chacha20::variants::Ietf;
39use chacha20::{ChaChaCore, R20};
40use subtle::ConstantTimeEq;
41use zeroize::Zeroize;
42
43use crate::constants::{
44 CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES, CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES,
45 CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX,
46 CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES,
47};
48use crate::error::Error;
49use crate::poly1305::{Key as Poly1305Key, Poly1305};
50use crate::rng::copy_randombytes;
51use crate::types::*;
52use crate::utils::pad16;
53
54pub type Mac = [u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES];
56pub type Nonce = [u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES];
58pub type Key = [u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES];
60
61const PAD0: [u8; 16] = [0u8; 16];
62
63pub fn crypto_aead_chacha20poly1305_ietf_keygen_inplace(key: &mut Key) {
65 copy_randombytes(key)
66}
67
68pub fn crypto_aead_chacha20poly1305_ietf_keygen() -> Key {
70 Key::generate()
71}
72
73fn validate_message_len(message_len: usize) -> Result<(), Error> {
74 if message_len > CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX {
75 Err(length_error!(
76 crate::ErrorContext::Message,
77 message_len,
78 max CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX
79 ))
80 } else {
81 Ok(())
82 }
83}
84
85fn validate_output_len(
86 output_len: usize,
87 expected_len: usize,
88 context: crate::ErrorContext,
89) -> Result<(), Error> {
90 if output_len != expected_len {
91 Err(length_error!(context, output_len, exact expected_len))
92 } else {
93 Ok(())
94 }
95}
96
97fn message_len_from_combined_len(
98 combined_len: usize,
99 context: crate::ErrorContext,
100) -> Result<usize, Error> {
101 if combined_len < CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES {
102 Err(length_error!(context, combined_len, min CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES))
103 } else {
104 let message_len = combined_len - CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES;
105 validate_message_len(message_len)?;
106 Ok(message_len)
107 }
108}
109
110type ChaCha20IetfCore = ChaChaCore<R20, Ietf>;
111
112fn apply_chacha20_ietf_keystream(data: &mut [u8], counter: u32, nonce: &Nonce, key: &Key) {
113 let available_blocks = u64::from(u32::MAX) - u64::from(counter) + 1;
114 debug_assert!((data.len() as u64) <= available_blocks * 64);
115
116 let mut cipher = ChaCha20IetfCore::new(key.into(), nonce.into());
117 cipher.set_block_pos(counter);
118
119 let (blocks, tail) = Array::<u8, U64>::slice_as_chunks_mut(data);
124 cipher.apply_keystream_blocks(blocks);
125 if !tail.is_empty() {
126 let mut block = Block::<ChaCha20IetfCore>::default();
127 cipher.write_keystream_block(&mut block);
128 for (byte, keystream_byte) in tail.iter_mut().zip(block.iter()) {
129 *byte ^= keystream_byte;
130 }
131 block.zeroize();
132 }
133}
134
135fn poly1305_key(nonce: &Nonce, key: &Key) -> Poly1305Key {
136 let mut mac_key = Poly1305Key::new();
137 apply_chacha20_ietf_keystream(&mut mac_key, 0, nonce, key);
138 mac_key
139}
140
141fn compute_mac(mac: &mut Mac, mac_key: &mut Poly1305Key, ciphertext: &[u8], ad: &[u8]) {
142 let mut state = Poly1305::new(mac_key);
143 mac_key.zeroize();
144
145 state.update(ad);
146 state.update(&PAD0[..pad16(ad.len())]);
147 state.update(ciphertext);
148 state.update(&PAD0[..pad16(ciphertext.len())]);
149 state.update(&(ad.len() as u64).to_le_bytes());
150 state.update(&(ciphertext.len() as u64).to_le_bytes());
151 state.finalize(mac);
152}
153
154fn compute_mac_to_array(mac_key: &mut Poly1305Key, ciphertext: &[u8], ad: &[u8]) -> Mac {
155 let mut mac = Mac::default();
156 compute_mac(&mut mac, mac_key, ciphertext, ad);
157 mac
158}
159
160fn verify_mac(mac: &Mac, computed_mac: &Mac) -> Result<(), Error> {
161 if mac.ct_eq(computed_mac).unwrap_u8() == 1 {
162 Ok(())
163 } else {
164 Err(Error::AuthenticationFailed)
165 }
166}
167
168pub fn crypto_aead_chacha20poly1305_ietf_encrypt_detached(
178 ciphertext: &mut [u8],
179 mac: &mut Mac,
180 message: &[u8],
181 associated_data: Option<&[u8]>,
182 nonce: &Nonce,
183 key: &Key,
184) -> Result<(), Error> {
185 validate_message_len(message.len())?;
186 validate_output_len(
187 ciphertext.len(),
188 message.len(),
189 crate::ErrorContext::Ciphertext,
190 )?;
191
192 let associated_data = associated_data.unwrap_or(&[]);
193 let mut mac_key = poly1305_key(nonce, key);
194
195 ciphertext.copy_from_slice(message);
196 apply_chacha20_ietf_keystream(ciphertext, 1, nonce, key);
197
198 compute_mac(mac, &mut mac_key, ciphertext, associated_data);
199 Ok(())
200}
201
202pub fn crypto_aead_chacha20poly1305_ietf_encrypt_detached_inplace(
209 data: &mut [u8],
210 mac: &mut Mac,
211 associated_data: Option<&[u8]>,
212 nonce: &Nonce,
213 key: &Key,
214) -> Result<(), Error> {
215 validate_message_len(data.len())?;
216
217 let associated_data = associated_data.unwrap_or(&[]);
218 let mut mac_key = poly1305_key(nonce, key);
219
220 apply_chacha20_ietf_keystream(data, 1, nonce, key);
221
222 compute_mac(mac, &mut mac_key, data, associated_data);
223 Ok(())
224}
225
226pub fn crypto_aead_chacha20poly1305_ietf_decrypt_detached(
236 message: &mut [u8],
237 ciphertext: &[u8],
238 mac: &Mac,
239 associated_data: Option<&[u8]>,
240 nonce: &Nonce,
241 key: &Key,
242) -> Result<(), Error> {
243 validate_message_len(ciphertext.len())?;
244 validate_output_len(
245 message.len(),
246 ciphertext.len(),
247 crate::ErrorContext::Message,
248 )?;
249
250 let associated_data = associated_data.unwrap_or(&[]);
251 let mut mac_key = poly1305_key(nonce, key);
252 let computed_mac = compute_mac_to_array(&mut mac_key, ciphertext, associated_data);
253
254 verify_mac(mac, &computed_mac)?;
255 message.copy_from_slice(ciphertext);
256 apply_chacha20_ietf_keystream(message, 1, nonce, key);
257 Ok(())
258}
259
260pub fn crypto_aead_chacha20poly1305_ietf_decrypt_detached_inplace(
268 data: &mut [u8],
269 mac: &Mac,
270 associated_data: Option<&[u8]>,
271 nonce: &Nonce,
272 key: &Key,
273) -> Result<(), Error> {
274 validate_message_len(data.len())?;
275
276 let associated_data = associated_data.unwrap_or(&[]);
277 let mut mac_key = poly1305_key(nonce, key);
278 let computed_mac = compute_mac_to_array(&mut mac_key, data, associated_data);
279
280 verify_mac(mac, &computed_mac)?;
281 apply_chacha20_ietf_keystream(data, 1, nonce, key);
282 Ok(())
283}
284
285pub fn crypto_aead_chacha20poly1305_ietf_encrypt(
294 ciphertext: &mut [u8],
295 message: &[u8],
296 associated_data: Option<&[u8]>,
297 nonce: &Nonce,
298 key: &Key,
299) -> Result<(), Error> {
300 validate_message_len(message.len())?;
301 validate_output_len(
302 ciphertext.len(),
303 message.len() + CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES,
304 crate::ErrorContext::Ciphertext,
305 )?;
306
307 let (ciphertext, mac) = ciphertext.split_at_mut(message.len());
308 let mac = MutByteArray::as_mut_array(mac);
309 crypto_aead_chacha20poly1305_ietf_encrypt_detached(
310 ciphertext,
311 mac,
312 message,
313 associated_data,
314 nonce,
315 key,
316 )
317}
318
319pub fn crypto_aead_chacha20poly1305_ietf_decrypt(
328 message: &mut [u8],
329 ciphertext: &[u8],
330 associated_data: Option<&[u8]>,
331 nonce: &Nonce,
332 key: &Key,
333) -> Result<(), Error> {
334 let message_len =
335 message_len_from_combined_len(ciphertext.len(), crate::ErrorContext::Ciphertext)?;
336 validate_output_len(message.len(), message_len, crate::ErrorContext::Message)?;
337
338 let (ciphertext, mac) = ciphertext.split_at(message_len);
339 let mac = ByteArray::as_array(mac);
340 crypto_aead_chacha20poly1305_ietf_decrypt_detached(
341 message,
342 ciphertext,
343 mac,
344 associated_data,
345 nonce,
346 key,
347 )
348}
349
350pub fn crypto_aead_chacha20poly1305_ietf_encrypt_inplace(
360 data: &mut [u8],
361 associated_data: Option<&[u8]>,
362 nonce: &Nonce,
363 key: &Key,
364) -> Result<(), Error> {
365 let message_len = message_len_from_combined_len(data.len(), crate::ErrorContext::Data)?;
366 let (data, mac) = data.split_at_mut(message_len);
367 let mac = MutByteArray::as_mut_array(mac);
368 crypto_aead_chacha20poly1305_ietf_encrypt_detached_inplace(
369 data,
370 mac,
371 associated_data,
372 nonce,
373 key,
374 )
375}
376
377pub fn crypto_aead_chacha20poly1305_ietf_decrypt_inplace(
387 data: &mut [u8],
388 associated_data: Option<&[u8]>,
389 nonce: &Nonce,
390 key: &Key,
391) -> Result<(), Error> {
392 let message_len = message_len_from_combined_len(data.len(), crate::ErrorContext::Data)?;
393 let (data, mac) = data.split_at_mut(message_len);
394 let mac = ByteArray::as_array(mac);
395 crypto_aead_chacha20poly1305_ietf_decrypt_detached_inplace(
396 data,
397 mac,
398 associated_data,
399 nonce,
400 key,
401 )
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 const MESSAGE: &[u8] =
409 b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
410 const AD: &[u8] = &[
411 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
412 ];
413 const KEY: Key = [
414 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e,
415 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d,
416 0x9e, 0x9f,
417 ];
418 const NONCE: Nonce = [
419 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
420 ];
421
422 fn expected() -> Vec<u8> {
423 hex::decode(concat!(
424 "d31a8d34648e60db7b86afbc53ef7ec2",
425 "a4aded51296e08fea9e2b5a736ee62d6",
426 "3dbea45e8ca9671282fafb69da92728b",
427 "1a71de0a9e060b2905d6a5b67ecd3b36",
428 "92ddbd7f2d778b8c9803aee328091b58",
429 "fab324e4fad675945585808b4831d7bc",
430 "3ff4def08e4b7a9de576d26586cec64b",
431 "61161ae10b594f09e26a7e902ecbd0600691"
432 ))
433 .expect("valid test vector")
434 }
435
436 #[test]
437 fn test_rfc_8439_known_answer() {
438 let mut ciphertext = vec![0u8; MESSAGE.len() + CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES];
439 crypto_aead_chacha20poly1305_ietf_encrypt(&mut ciphertext, MESSAGE, Some(AD), &NONCE, &KEY)
440 .expect("encrypt");
441 assert_eq!(ciphertext, expected());
442
443 let mut decrypted = vec![0u8; MESSAGE.len()];
444 crypto_aead_chacha20poly1305_ietf_decrypt(
445 &mut decrypted,
446 &ciphertext,
447 Some(AD),
448 &NONCE,
449 &KEY,
450 )
451 .expect("decrypt");
452 assert_eq!(decrypted, MESSAGE);
453 }
454
455 #[test]
456 fn test_detached_and_inplace_match_combined() {
457 let expected = expected();
458 let mut detached = MESSAGE.to_vec();
459 let mut mac = Mac::default();
460 crypto_aead_chacha20poly1305_ietf_encrypt_detached_inplace(
461 &mut detached,
462 &mut mac,
463 Some(AD),
464 &NONCE,
465 &KEY,
466 )
467 .expect("detached encrypt");
468 assert_eq!(detached, expected[..MESSAGE.len()]);
469 assert_eq!(mac, expected[MESSAGE.len()..]);
470
471 crypto_aead_chacha20poly1305_ietf_decrypt_detached_inplace(
472 &mut detached,
473 &mac,
474 Some(AD),
475 &NONCE,
476 &KEY,
477 )
478 .expect("detached decrypt");
479 assert_eq!(detached, MESSAGE);
480
481 let mut combined = MESSAGE.to_vec();
482 combined.resize(MESSAGE.len() + CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES, 0);
483 crypto_aead_chacha20poly1305_ietf_encrypt_inplace(&mut combined, Some(AD), &NONCE, &KEY)
484 .expect("in-place encrypt");
485 assert_eq!(combined, expected);
486 }
487
488 #[test]
489 fn test_authentication_failure_does_not_mutate_output() {
490 let mut ciphertext = expected();
491 ciphertext[0] ^= 1;
492 let mut plaintext = vec![0xa5; MESSAGE.len()];
493 let original = plaintext.clone();
494
495 assert!(matches!(
496 crypto_aead_chacha20poly1305_ietf_decrypt(
497 &mut plaintext,
498 &ciphertext,
499 Some(AD),
500 &NONCE,
501 &KEY,
502 ),
503 Err(Error::AuthenticationFailed)
504 ));
505 assert_eq!(plaintext, original);
506
507 let mut inplace = ciphertext;
508 let original = inplace.clone();
509 assert!(matches!(
510 crypto_aead_chacha20poly1305_ietf_decrypt_inplace(&mut inplace, Some(AD), &NONCE, &KEY,),
511 Err(Error::AuthenticationFailed)
512 ));
513 assert_eq!(inplace, original);
514
515 let mut plaintext = vec![0xa5; MESSAGE.len()];
516 assert!(matches!(
517 crypto_aead_chacha20poly1305_ietf_decrypt(
518 &mut plaintext,
519 &expected(),
520 Some(b"wrong associated data"),
521 &NONCE,
522 &KEY,
523 ),
524 Err(Error::AuthenticationFailed)
525 ));
526 assert_eq!(plaintext, vec![0xa5; MESSAGE.len()]);
527 }
528
529 #[test]
530 fn test_empty_message_and_length_errors() {
531 let mut ciphertext = [0u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES];
532 crypto_aead_chacha20poly1305_ietf_encrypt(&mut ciphertext, &[], None, &NONCE, &KEY)
533 .expect("empty encrypt");
534 crypto_aead_chacha20poly1305_ietf_decrypt(&mut [], &ciphertext, None, &NONCE, &KEY)
535 .expect("empty decrypt");
536
537 assert!(
538 crypto_aead_chacha20poly1305_ietf_decrypt(
539 &mut [],
540 &[0u8; CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES - 1],
541 None,
542 &NONCE,
543 &KEY,
544 )
545 .is_err()
546 );
547 assert!(
548 crypto_aead_chacha20poly1305_ietf_encrypt(&mut [0u8; 1], &[], None, &NONCE, &KEY,)
549 .is_err()
550 );
551 }
552
553 #[test]
554 fn test_final_counter_block_is_available() {
555 let mut block = [0u8; 64];
556 apply_chacha20_ietf_keystream(&mut block, u32::MAX, &NONCE, &KEY);
557 assert_ne!(block, [0u8; 64]);
558
559 apply_chacha20_ietf_keystream(&mut block, u32::MAX, &NONCE, &KEY);
560 assert_eq!(block, [0u8; 64]);
561 }
562
563 #[cfg(dryoc_native_tests)]
564 #[test]
565 fn test_final_counter_block_matches_libsodium() {
566 use libsodium_sys::crypto_stream_chacha20_ietf_xor_ic;
567
568 let message = [0xa5u8; 64];
569 let mut actual = message;
570 apply_chacha20_ietf_keystream(&mut actual, u32::MAX, &NONCE, &KEY);
571
572 let mut expected = [0u8; 64];
573 let result = unsafe {
576 crypto_stream_chacha20_ietf_xor_ic(
577 expected.as_mut_ptr(),
578 message.as_ptr(),
579 message.len() as u64,
580 NONCE.as_ptr(),
581 u32::MAX,
582 KEY.as_ptr(),
583 )
584 };
585 assert_eq!(result, 0);
586 assert_eq!(actual, expected);
587 }
588
589 #[cfg(dryoc_native_tests)]
590 #[test]
591 fn test_libsodium_constants() {
592 use libsodium_sys::{
593 crypto_aead_chacha20poly1305_ietf_abytes, crypto_aead_chacha20poly1305_ietf_keybytes,
594 crypto_aead_chacha20poly1305_ietf_messagebytes_max,
595 crypto_aead_chacha20poly1305_ietf_npubbytes,
596 crypto_aead_chacha20poly1305_ietf_nsecbytes,
597 };
598
599 use crate::constants::CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES;
600
601 unsafe {
604 assert_eq!(
605 crypto_aead_chacha20poly1305_ietf_keybytes(),
606 CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES
607 );
608 assert_eq!(
609 crypto_aead_chacha20poly1305_ietf_nsecbytes(),
610 CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES
611 );
612 assert_eq!(
613 crypto_aead_chacha20poly1305_ietf_npubbytes(),
614 CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES
615 );
616 assert_eq!(
617 crypto_aead_chacha20poly1305_ietf_abytes(),
618 CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES
619 );
620 assert_eq!(
621 crypto_aead_chacha20poly1305_ietf_messagebytes_max(),
622 CRYPTO_AEAD_CHACHA20POLY1305_IETF_MESSAGEBYTES_MAX
623 );
624 }
625 }
626
627 #[cfg(dryoc_native_tests)]
628 #[test]
629 fn test_sodiumoxide_interop() {
630 use sodiumoxide::crypto::aead::chacha20poly1305_ietf::{
631 Key as SodiumKey, Nonce as SodiumNonce, open, seal,
632 };
633
634 let sodium_key = SodiumKey::from_slice(&KEY).expect("key");
635 let sodium_nonce = SodiumNonce::from_slice(&NONCE).expect("nonce");
636 let ciphertext = expected();
637 assert_eq!(
638 open(&ciphertext, Some(AD), &sodium_nonce, &sodium_key).expect("sodiumoxide open"),
639 MESSAGE
640 );
641
642 let sodium_ciphertext = seal(MESSAGE, Some(AD), &sodium_nonce, &sodium_key);
643 let mut plaintext = vec![0u8; MESSAGE.len()];
644 crypto_aead_chacha20poly1305_ietf_decrypt(
645 &mut plaintext,
646 &sodium_ciphertext,
647 Some(AD),
648 &NONCE,
649 &KEY,
650 )
651 .expect("dryoc decrypt");
652 assert_eq!(plaintext, MESSAGE);
653 }
654}