1 //! Core AEAD cipher implementation for (X)ChaCha20Poly1305. 2 3 use ::cipher::{StreamCipher, StreamCipherSeek}; 4 use aead::generic_array::GenericArray; 5 use aead::Error; 6 use core::convert::TryInto; 7 use poly1305::{ 8 universal_hash::{NewUniversalHash, UniversalHash}, 9 Poly1305, 10 }; 11 use zeroize::Zeroize; 12 13 use super::Tag; 14 15 /// Size of a ChaCha20 block in bytes 16 const BLOCK_SIZE: usize = 64; 17 18 /// Maximum number of blocks that can be encrypted with ChaCha20 before the 19 /// counter overflows. 20 const MAX_BLOCKS: usize = core::u32::MAX as usize; 21 22 /// ChaCha20Poly1305 instantiated with a particular nonce 23 pub(crate) struct Cipher<C> 24 where 25 C: StreamCipher + StreamCipherSeek, 26 { 27 cipher: C, 28 mac: Poly1305, 29 } 30 31 impl<C> Cipher<C> 32 where 33 C: StreamCipher + StreamCipherSeek, 34 { 35 /// Instantiate the underlying cipher with a particular nonce new(mut cipher: C) -> Self36 pub(crate) fn new(mut cipher: C) -> Self { 37 // Derive Poly1305 key from the first 32-bytes of the ChaCha20 keystream 38 let mut mac_key = poly1305::Key::default(); 39 cipher.apply_keystream(&mut *mac_key); 40 let mac = Poly1305::new(GenericArray::from_slice(&*mac_key)); 41 mac_key.zeroize(); 42 43 // Set ChaCha20 counter to 1 44 cipher.seek(BLOCK_SIZE as u64); 45 46 Self { cipher, mac } 47 } 48 49 /// Encrypt the given message in-place, returning the authentication tag encrypt_in_place_detached( mut self, associated_data: &[u8], buffer: &mut [u8], ) -> Result<Tag, Error>50 pub(crate) fn encrypt_in_place_detached( 51 mut self, 52 associated_data: &[u8], 53 buffer: &mut [u8], 54 ) -> Result<Tag, Error> { 55 if buffer.len() / BLOCK_SIZE >= MAX_BLOCKS { 56 return Err(Error); 57 } 58 59 self.mac.update_padded(associated_data); 60 61 // TODO(tarcieri): interleave encryption with Poly1305 62 // See: <https://github.com/RustCrypto/AEADs/issues/74> 63 self.cipher.apply_keystream(buffer); 64 self.mac.update_padded(buffer); 65 66 self.authenticate_lengths(associated_data, buffer)?; 67 Ok(self.mac.finalize().into_bytes()) 68 } 69 70 /// Decrypt the given message, first authenticating ciphertext integrity 71 /// and returning an error if it's been tampered with. decrypt_in_place_detached( mut self, associated_data: &[u8], buffer: &mut [u8], tag: &Tag, ) -> Result<(), Error>72 pub(crate) fn decrypt_in_place_detached( 73 mut self, 74 associated_data: &[u8], 75 buffer: &mut [u8], 76 tag: &Tag, 77 ) -> Result<(), Error> { 78 if buffer.len() / BLOCK_SIZE >= MAX_BLOCKS { 79 return Err(Error); 80 } 81 82 self.mac.update_padded(associated_data); 83 self.mac.update_padded(buffer); 84 self.authenticate_lengths(associated_data, buffer)?; 85 86 // This performs a constant-time comparison using the `subtle` crate 87 if self.mac.verify(tag).is_ok() { 88 // TODO(tarcieri): interleave decryption with Poly1305 89 // See: <https://github.com/RustCrypto/AEADs/issues/74> 90 self.cipher.apply_keystream(buffer); 91 Ok(()) 92 } else { 93 Err(Error) 94 } 95 } 96 97 /// Authenticate the lengths of the associated data and message authenticate_lengths(&mut self, associated_data: &[u8], buffer: &[u8]) -> Result<(), Error>98 fn authenticate_lengths(&mut self, associated_data: &[u8], buffer: &[u8]) -> Result<(), Error> { 99 let associated_data_len: u64 = associated_data.len().try_into().map_err(|_| Error)?; 100 let buffer_len: u64 = buffer.len().try_into().map_err(|_| Error)?; 101 102 let mut block = GenericArray::default(); 103 block[..8].copy_from_slice(&associated_data_len.to_le_bytes()); 104 block[8..].copy_from_slice(&buffer_len.to_le_bytes()); 105 self.mac.update(&block); 106 107 Ok(()) 108 } 109 } 110