From f22c55eb681d9cb876176c9e2a014a449e6ddc51 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 4 Oct 2019 11:04:21 -0700 Subject: [PATCH] Expose "detached" in-place encryption/decryption APIs Exposes an `encrypt_in_place_detached()` and `decrypt_in_place_detached()` API on all AEAD implementations in this repository. Until we figure out what the upstream API looks like, this commit at least makes an in-place mode an option for those who don't want to allocate a buffer for every encryption/decryption operation. --- aes-gcm-siv/Cargo.toml | 1 + aes-gcm-siv/src/ctr32.rs | 3 +- aes-gcm-siv/src/lib.rs | 111 ++++++++++++++-------- chacha20poly1305/src/cipher.rs | 26 ++--- chacha20poly1305/src/lib.rs | 33 +++++++ chacha20poly1305/src/xchacha20poly1305.rs | 32 ++++++- xsalsa20poly1305/src/lib.rs | 100 ++++++++++++------- 7 files changed, 218 insertions(+), 88 deletions(-) diff --git a/aes-gcm-siv/Cargo.toml b/aes-gcm-siv/Cargo.toml index c3a0d217..d0565fa4 100644 --- a/aes-gcm-siv/Cargo.toml +++ b/aes-gcm-siv/Cargo.toml @@ -20,6 +20,7 @@ aes = "0.3" ctr = "0.3" polyval = "0.2" subtle = { version = "2", default-features = false } +zeroize = { version = "1.0.0-pre", default-features = false } [dev-dependencies] criterion = "0.3.0" diff --git a/aes-gcm-siv/src/ctr32.rs b/aes-gcm-siv/src/ctr32.rs index 47d1353b..5427255f 100644 --- a/aes-gcm-siv/src/ctr32.rs +++ b/aes-gcm-siv/src/ctr32.rs @@ -32,7 +32,8 @@ where C: BlockCipher, { /// Instantiate a new CTR instance - pub fn new(cipher: &'c C, mut counter_block: Block128) -> Self { + pub fn new(cipher: &'c C, counter_block: &Block128) -> Self { + let mut counter_block = *counter_block; counter_block[15] |= 0x80; Self { diff --git a/aes-gcm-siv/src/lib.rs b/aes-gcm-siv/src/lib.rs index e72756c2..92fc0fe0 100644 --- a/aes-gcm-siv/src/lib.rs +++ b/aes-gcm-siv/src/lib.rs @@ -66,6 +66,7 @@ use aead::{Aead, Error, NewAead, Payload}; use aes::{block_cipher_trait::BlockCipher, Aes128, Aes256}; use alloc::vec::Vec; use polyval::{universal_hash::UniversalHash, Polyval}; +use zeroize::Zeroize; /// Maximum length of associated data (from RFC 8452 Section 6) pub const A_MAX: u64 = 1 << 36; @@ -77,7 +78,7 @@ pub const P_MAX: u64 = 1 << 36; pub const C_MAX: u64 = (1 << 36) + 16; /// AES-GCM-SIV tags -type Tag = GenericArray; +pub type Tag = GenericArray; /// AES-GCM-SIV with a 128-bit key pub type Aes128GcmSiv = AesGcmSiv; @@ -116,7 +117,13 @@ where nonce: &GenericArray, plaintext: impl Into>, ) -> Result, Error> { - Cipher::::new(&self.key, nonce).encrypt(plaintext.into()) + let payload = plaintext.into(); + let mut buffer = Vec::with_capacity(payload.msg.len() + Self::TagSize::to_usize()); + buffer.extend_from_slice(payload.msg); + + let tag = self.encrypt_in_place_detached(nonce, payload.aad, &mut buffer)?; + buffer.extend_from_slice(tag.as_slice()); + Ok(buffer) } fn decrypt<'msg, 'aad>( @@ -124,7 +131,46 @@ where nonce: &GenericArray, ciphertext: impl Into>, ) -> Result, Error> { - Cipher::::new(&self.key, nonce).decrypt(ciphertext.into()) + let payload = ciphertext.into(); + + if payload.msg.len() < Self::TagSize::to_usize() { + return Err(Error); + } + + let tag_start = payload.msg.len() - Self::TagSize::to_usize(); + let mut buffer = Vec::from(&payload.msg[..tag_start]); + let tag = Tag::from_slice(&payload.msg[tag_start..]); + self.decrypt_in_place_detached(nonce, payload.aad, &mut buffer, tag)?; + + Ok(buffer) + } +} + +impl AesGcmSiv +where + C: BlockCipher, +{ + /// Encrypt the data in-place, returning the authentication tag + pub fn encrypt_in_place_detached( + &self, + nonce: &GenericArray::NonceSize>, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result { + Cipher::::new(&self.key, nonce).encrypt_in_place_detached(associated_data, buffer) + } + + /// Decrypt the data in-place, returning an error in the event the provided + /// authentication tag does not match the given ciphertext (i.e. ciphertext + /// is modified/unauthentic) + pub fn decrypt_in_place_detached( + &self, + nonce: &GenericArray::NonceSize>, + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<(), Error> { + Cipher::::new(&self.key, nonce).decrypt_in_place_detached(associated_data, buffer, tag) } } @@ -149,7 +195,6 @@ where pub(crate) fn new(key: &GenericArray, nonce: &GenericArray) -> Self { let key_generating_key = C::new(key); - // TODO(tarcieri): zeroize all of these buffers! let mut mac_key = GenericArray::default(); let mut enc_key = GenericArray::default(); let mut block = GenericArray::default(); @@ -183,63 +228,43 @@ where } } - Self { + let result = Self { enc_cipher: C::new(&enc_key), polyval: Polyval::new(&mac_key), nonce: *nonce, - } - } + }; - /// Encrypt the given message, allocating a vector for the resulting ciphertext - pub(crate) fn encrypt(self, payload: Payload<'_, '_>) -> Result, Error> { - let tag_size = ::BlockSize::to_usize(); + // Zeroize all intermediate buffers + // TODO(tarcieri): use `Zeroizing` when const generics land + mac_key.as_mut_slice().zeroize(); + enc_key.as_mut_slice().zeroize(); + block.as_mut_slice().zeroize(); - let mut buffer = Vec::with_capacity(payload.msg.len() + tag_size); - buffer.extend_from_slice(payload.msg); - - let tag = self.encrypt_in_place(&mut buffer, payload.aad)?; - buffer.extend_from_slice(tag.as_slice()); - Ok(buffer) + result } /// Encrypt the given message in-place, returning the authentication tag - pub(crate) fn encrypt_in_place( + pub(crate) fn encrypt_in_place_detached( mut self, - buffer: &mut [u8], associated_data: &[u8], + buffer: &mut [u8], ) -> Result { if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX { return Err(Error); } let tag = self.compute_tag(associated_data, buffer); - Ctr32::new(&self.enc_cipher, tag).apply_keystream(buffer); + Ctr32::new(&self.enc_cipher, &tag).apply_keystream(buffer); Ok(tag) } - /// Decrypt the given message, allocating a vector for the resulting plaintext - pub(crate) fn decrypt(self, payload: Payload<'_, '_>) -> Result, Error> { - let tag_size = ::BlockSize::to_usize(); - - if payload.msg.len() < tag_size { - return Err(Error); - } - - let tag_start = payload.msg.len() - tag_size; - let mut buffer = Vec::from(&payload.msg[..tag_start]); - let tag = GenericArray::from_slice(&payload.msg[tag_start..]); - self.decrypt_in_place(&mut buffer, payload.aad, *tag)?; - - Ok(buffer) - } - /// Decrypt the given message, first authenticating ciphertext integrity /// and returning an error if it's been tampered with. - pub(crate) fn decrypt_in_place( + pub(crate) fn decrypt_in_place_detached( mut self, - buffer: &mut [u8], associated_data: &[u8], - tag: Tag, + buffer: &mut [u8], + tag: &Tag, ) -> Result<(), Error> { if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX { return Err(Error); @@ -290,9 +315,19 @@ where *byte ^= self.nonce[i]; } + // Clear the highest bit tag[15] &= 0x7f; self.enc_cipher.encrypt_block(&mut tag); tag } } + +impl Drop for AesGcmSiv +where + C: BlockCipher, +{ + fn drop(&mut self) { + self.key.as_mut_slice().zeroize(); + } +} diff --git a/chacha20poly1305/src/cipher.rs b/chacha20poly1305/src/cipher.rs index d535e93e..bfe6ce75 100644 --- a/chacha20poly1305/src/cipher.rs +++ b/chacha20poly1305/src/cipher.rs @@ -5,9 +5,11 @@ use aead::{Error, Payload}; use alloc::vec::Vec; use chacha20::stream_cipher::{SyncStreamCipher, SyncStreamCipherSeek}; use core::convert::TryInto; -use poly1305::{universal_hash::UniversalHash, Poly1305, Tag}; +use poly1305::{universal_hash::UniversalHash, Poly1305}; use zeroize::Zeroizing; +use super::Tag; + /// ChaCha20Poly1305 instantiated with a particular nonce pub(crate) struct Cipher where @@ -39,16 +41,16 @@ where let mut buffer = Vec::with_capacity(payload.msg.len() + poly1305::BLOCK_SIZE); buffer.extend_from_slice(payload.msg); - let tag = self.encrypt_in_place(&mut buffer, payload.aad)?; - buffer.extend_from_slice(tag.into_bytes().as_slice()); + let tag = self.encrypt_in_place_detached(payload.aad, &mut buffer)?; + buffer.extend_from_slice(tag.as_slice()); Ok(buffer) } /// Encrypt the given message in-place, returning the authentication tag - pub(crate) fn encrypt_in_place( + pub(crate) fn encrypt_in_place_detached( mut self, - buffer: &mut [u8], associated_data: &[u8], + buffer: &mut [u8], ) -> Result { if buffer.len() / chacha20::BLOCK_SIZE >= chacha20::MAX_BLOCKS { return Err(Error); @@ -58,7 +60,7 @@ where self.cipher.apply_keystream(buffer); self.mac.update_padded(buffer); self.authenticate_lengths(associated_data, buffer)?; - Ok(self.mac.result()) + Ok(self.mac.result().into_bytes()) } /// Decrypt the given message, allocating a vector for the resulting plaintext @@ -69,19 +71,19 @@ where let tag_start = payload.msg.len() - poly1305::BLOCK_SIZE; let mut buffer = Vec::from(&payload.msg[..tag_start]); - let tag: [u8; poly1305::BLOCK_SIZE] = payload.msg[tag_start..].try_into().unwrap(); - self.decrypt_in_place(&mut buffer, payload.aad, &tag)?; + let tag = Tag::from_slice(&payload.msg[tag_start..]); + self.decrypt_in_place_detached(payload.aad, &mut buffer, tag)?; Ok(buffer) } /// Decrypt the given message, first authenticating ciphertext integrity /// and returning an error if it's been tampered with. - pub(crate) fn decrypt_in_place( + pub(crate) fn decrypt_in_place_detached( mut self, - buffer: &mut [u8], associated_data: &[u8], - tag: &[u8; poly1305::BLOCK_SIZE], + buffer: &mut [u8], + tag: &Tag, ) -> Result<(), Error> { if buffer.len() / chacha20::BLOCK_SIZE >= chacha20::MAX_BLOCKS { return Err(Error); @@ -92,7 +94,7 @@ where self.authenticate_lengths(associated_data, buffer)?; // This performs a constant-time comparison using the `subtle` crate - if self.mac.verify(GenericArray::from_slice(tag)).is_ok() { + if self.mac.verify(tag).is_ok() { self.cipher.apply_keystream(buffer); Ok(()) } else { diff --git a/chacha20poly1305/src/lib.rs b/chacha20poly1305/src/lib.rs index f671d5b4..820b23d4 100644 --- a/chacha20poly1305/src/lib.rs +++ b/chacha20poly1305/src/lib.rs @@ -34,6 +34,9 @@ use alloc::vec::Vec; use chacha20::{stream_cipher::NewStreamCipher, ChaCha20}; use zeroize::Zeroize; +/// Poly1305 tags +pub type Tag = GenericArray; + /// ChaCha20Poly1305 Authenticated Encryption with Additional Data (AEAD) #[derive(Clone)] pub struct ChaCha20Poly1305 { @@ -71,6 +74,36 @@ impl Aead for ChaCha20Poly1305 { } } +impl ChaCha20Poly1305 { + /// Encrypt the data in-place, returning the authentication tag + pub fn encrypt_in_place_detached( + &self, + nonce: &GenericArray::NonceSize>, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result { + Cipher::new(ChaCha20::new(&self.key, nonce)) + .encrypt_in_place_detached(associated_data, buffer) + } + + /// Decrypt the data in-place, returning an error in the event the provided + /// authentication tag does not match the given ciphertext (i.e. ciphertext + /// is modified/unauthentic) + pub fn decrypt_in_place_detached( + &self, + nonce: &GenericArray::NonceSize>, + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<(), Error> { + Cipher::new(ChaCha20::new(&self.key, nonce)).decrypt_in_place_detached( + associated_data, + buffer, + tag, + ) + } +} + impl Drop for ChaCha20Poly1305 { fn drop(&mut self) { self.key.as_mut_slice().zeroize(); diff --git a/chacha20poly1305/src/xchacha20poly1305.rs b/chacha20poly1305/src/xchacha20poly1305.rs index 18970b15..2f6eef1f 100644 --- a/chacha20poly1305/src/xchacha20poly1305.rs +++ b/chacha20poly1305/src/xchacha20poly1305.rs @@ -1,6 +1,6 @@ //! XChaCha20Poly1305 is an extended nonce variant of ChaCha20Poly1305 -use crate::cipher::Cipher; +use crate::{cipher::Cipher, Tag}; use aead::generic_array::{ typenum::{U0, U16, U24, U32}, GenericArray, @@ -69,6 +69,36 @@ impl Aead for XChaCha20Poly1305 { } } +impl XChaCha20Poly1305 { + /// Encrypt the data in-place, returning the authentication tag + pub fn encrypt_in_place_detached( + &self, + nonce: &GenericArray::NonceSize>, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result { + Cipher::new(XChaCha20::new(&self.key, nonce)) + .encrypt_in_place_detached(associated_data, buffer) + } + + /// Decrypt the data in-place, returning an error in the event the provided + /// authentication tag does not match the given ciphertext (i.e. ciphertext + /// is modified/unauthentic) + pub fn decrypt_in_place_detached( + &self, + nonce: &GenericArray::NonceSize>, + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<(), Error> { + Cipher::new(XChaCha20::new(&self.key, nonce)).decrypt_in_place_detached( + associated_data, + buffer, + tag, + ) + } +} + impl Drop for XChaCha20Poly1305 { fn drop(&mut self) { self.key.as_mut_slice().zeroize(); diff --git a/xsalsa20poly1305/src/lib.rs b/xsalsa20poly1305/src/lib.rs index d8ee4f84..f1bea9dd 100644 --- a/xsalsa20poly1305/src/lib.rs +++ b/xsalsa20poly1305/src/lib.rs @@ -29,12 +29,14 @@ use aead::generic_array::{ }; use aead::{Aead, Error, NewAead, Payload}; use alloc::vec::Vec; -use core::convert::TryInto; -use poly1305::{universal_hash::UniversalHash, Poly1305, Tag}; +use poly1305::{universal_hash::UniversalHash, Poly1305}; use salsa20::stream_cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek}; use salsa20::XSalsa20; use zeroize::{Zeroize, Zeroizing}; +/// Poly1305 tags +pub type Tag = GenericArray; + /// **XSalsa20Poly1305** (a.k.a. NaCl `crypto_secretbox`) authenticated /// encryption cipher. #[derive(Clone)] @@ -61,7 +63,18 @@ impl Aead for XSalsa20Poly1305 { nonce: &GenericArray, plaintext: impl Into>, ) -> Result, Error> { - Cipher::new(XSalsa20::new(&self.key, nonce)).encrypt(plaintext.into()) + let payload = plaintext.into(); + let mut buffer = Vec::with_capacity(payload.msg.len() + poly1305::BLOCK_SIZE); + buffer.extend_from_slice(&[0u8; poly1305::BLOCK_SIZE]); + buffer.extend_from_slice(payload.msg); + + let tag = self.encrypt_in_place_detached( + nonce, + payload.aad, + &mut buffer[poly1305::BLOCK_SIZE..], + )?; + buffer[..poly1305::BLOCK_SIZE].copy_from_slice(tag.as_slice()); + Ok(buffer) } fn decrypt<'msg, 'aad>( @@ -69,7 +82,47 @@ impl Aead for XSalsa20Poly1305 { nonce: &GenericArray, ciphertext: impl Into>, ) -> Result, Error> { - Cipher::new(XSalsa20::new(&self.key, nonce)).decrypt(ciphertext.into()) + let payload = ciphertext.into(); + + if payload.msg.len() < poly1305::BLOCK_SIZE { + return Err(Error); + } + + let mut buffer = Vec::from(&payload.msg[poly1305::BLOCK_SIZE..]); + let tag = Tag::from_slice(&payload.msg[..poly1305::BLOCK_SIZE]); + self.decrypt_in_place_detached(nonce, payload.aad, &mut buffer, &tag)?; + + Ok(buffer) + } +} + +impl XSalsa20Poly1305 { + /// Encrypt the data in-place, returning the authentication tag + pub fn encrypt_in_place_detached( + &self, + nonce: &GenericArray::NonceSize>, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result { + Cipher::new(XSalsa20::new(&self.key, nonce)) + .encrypt_in_place_detached(associated_data, buffer) + } + + /// Decrypt the data in-place, returning an error in the event the provided + /// authentication tag does not match the given ciphertext (i.e. ciphertext + /// is modified/unauthentic) + pub fn decrypt_in_place_detached( + &self, + nonce: &GenericArray::NonceSize>, + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<(), Error> { + Cipher::new(XSalsa20::new(&self.key, nonce)).decrypt_in_place_detached( + associated_data, + buffer, + tag, + ) } } @@ -102,22 +155,11 @@ where Self { cipher, mac } } - /// Encrypt the given message, allocating a vector for the resulting ciphertext - pub(crate) fn encrypt(self, payload: Payload) -> Result, Error> { - let mut buffer = Vec::with_capacity(payload.msg.len() + poly1305::BLOCK_SIZE); - buffer.extend_from_slice(&[0u8; poly1305::BLOCK_SIZE]); - buffer.extend_from_slice(payload.msg); - - let tag = self.encrypt_in_place(&mut buffer[poly1305::BLOCK_SIZE..], payload.aad)?; - buffer[..poly1305::BLOCK_SIZE].copy_from_slice(tag.into_bytes().as_slice()); - Ok(buffer) - } - /// Encrypt the given message in-place, returning the authentication tag - pub(crate) fn encrypt_in_place( + pub(crate) fn encrypt_in_place_detached( mut self, - buffer: &mut [u8], associated_data: &[u8], + buffer: &mut [u8], ) -> Result { // XSalsa20Poly1305 doesn't support AAD if !associated_data.is_empty() { @@ -126,30 +168,16 @@ where self.cipher.apply_keystream(buffer); self.mac.update(buffer); - Ok(self.mac.result()) - } - - /// Decrypt the given message, allocating a vector for the resulting plaintext - pub(crate) fn decrypt(self, payload: Payload) -> Result, Error> { - if payload.msg.len() < poly1305::BLOCK_SIZE { - return Err(Error); - } - - let mut buffer = Vec::from(&payload.msg[poly1305::BLOCK_SIZE..]); - let tag: [u8; poly1305::BLOCK_SIZE] = - payload.msg[..poly1305::BLOCK_SIZE].try_into().unwrap(); - self.decrypt_in_place(&mut buffer, payload.aad, &tag)?; - - Ok(buffer) + Ok(self.mac.result().into_bytes()) } /// Decrypt the given message, first authenticating ciphertext integrity /// and returning an error if it's been tampered with. - pub(crate) fn decrypt_in_place( + pub(crate) fn decrypt_in_place_detached( mut self, - buffer: &mut [u8], associated_data: &[u8], - tag: &[u8; poly1305::BLOCK_SIZE], + buffer: &mut [u8], + tag: &Tag, ) -> Result<(), Error> { // XSalsa20Poly1305 doesn't support AAD if !associated_data.is_empty() { @@ -159,7 +187,7 @@ where self.mac.update(buffer); // This performs a constant-time comparison using the `subtle` crate - if self.mac.verify(GenericArray::from_slice(tag)).is_ok() { + if self.mac.verify(tag).is_ok() { self.cipher.apply_keystream(buffer); Ok(()) } else {