From 0f69094c774c3e058461d42dac932e577aa810d6 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Wed, 9 Sep 2020 19:58:58 +0200 Subject: [PATCH 1/7] eax: Introduce a streaming variant --- eax/Cargo.toml | 1 + eax/src/lib.rs | 82 +++--------- eax/src/stream.rs | 322 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+), 65 deletions(-) create mode 100644 eax/src/stream.rs diff --git a/eax/Cargo.toml b/eax/Cargo.toml index 14e997cd..e18193e6 100644 --- a/eax/Cargo.toml +++ b/eax/Cargo.toml @@ -31,6 +31,7 @@ aead = { version = "0.3", features = ["dev"], default-features = false } [features] default = ["alloc"] +std = ["aead/std"] alloc = ["aead/alloc"] heapless = ["aead/heapless"] diff --git a/eax/src/lib.rs b/eax/src/lib.rs index a5f8143c..7ff23df6 100644 --- a/eax/src/lib.rs +++ b/eax/src/lib.rs @@ -78,6 +78,9 @@ #![deny(unsafe_code)] #![warn(missing_docs, rust_2018_idioms)] +#[cfg(feature = "std")] +extern crate std; + pub use aead::{self, AeadInPlace, Error, NewAead}; use block_cipher::{ @@ -103,6 +106,8 @@ pub const C_MAX: u64 = (1 << 36) + 16; /// EAX tags pub type Tag = GenericArray; +pub mod stream; + /// EAX: generic over an underlying block cipher implementation. /// /// This type is generic to support substituting alternative cipher @@ -146,31 +151,18 @@ where associated_data: &[u8], buffer: &mut [u8], ) -> Result { + use stream::{EaxStream, Encrypt}; + if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX { return Err(Error); } - // https://crypto.stackexchange.com/questions/26948/eax-cipher-mode-with-nonce-equal-header - // has an explanation of eax. - - // l = block cipher size = 128 (for AES-128) = 16 byte - // 1. n ← OMAC(0 || Nonce) - // (the 0 means the number zero in l bits) - let n = Self::cmac_with_iv(&self.key, 0, nonce); - - // 2. h ← OMAC(1 || associated data) - let h = Self::cmac_with_iv(&self.key, 1, associated_data); + let mut eax = EaxStream::::with_key_and_nonce(&self.key, nonce); - // 3. enc ← CTR(M) using n as iv - let mut cipher = ctr::Ctr128::::from_block_cipher(Cipher::new(&self.key), &n); - cipher.apply_keystream(buffer); + eax.update_assoc(associated_data); + eax.encrypt(buffer); - // 4. c ← OMAC(2 || enc) - let c = Self::cmac_with_iv(&self.key, 2, buffer); - - // 5. tag ← n ^ h ^ c - // (^ means xor) - Ok(n.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b)) + Ok(eax.finish()) } fn decrypt_in_place_detached( @@ -180,57 +172,17 @@ where buffer: &mut [u8], tag: &Tag, ) -> Result<(), Error> { + use stream::{Decrypt, EaxStream}; + if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX { return Err(Error); } - // 1. n ← OMAC(0 || Nonce) - let n = Self::cmac_with_iv(&self.key, 0, nonce); - - // 2. h ← OMAC(1 || associated data) - let h = Self::cmac_with_iv(&self.key, 1, associated_data); - - // 4. c ← OMAC(2 || enc) - let c = Self::cmac_with_iv(&self.key, 2, buffer); + let mut eax = EaxStream::::with_key_and_nonce(&self.key, nonce); - // 5. tag ← n ^ h ^ c - // (^ means xor) - let expected_tag = n.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b); + eax.update_assoc(associated_data); + eax.decrypt(buffer); - let expected_tag = &expected_tag[..tag.len()]; - - // Check mac using secure comparison - use subtle::ConstantTimeEq; - if expected_tag.ct_eq(tag).unwrap_u8() == 1 { - // Decrypt - let mut cipher = ctr::Ctr128::::from_block_cipher(Cipher::new(&self.key), &n); - cipher.apply_keystream(buffer); - Ok(()) - } else { - Err(Error) - } - } -} - -impl Eax -where - Cipher: BlockCipher + NewBlockCipher + Clone, - Cipher::ParBlocks: ArrayLength>, -{ - /// CMAC/OMAC1 - /// - /// To avoid constructing new buffers on the heap, an iv encoded into 16 - /// bytes is prepended inside this function. - fn cmac_with_iv( - key: &GenericArray, - iv: u8, - data: &[u8], - ) -> GenericArray as Mac>::OutputSize> { - let mut mac = Cmac::::new(key); - mac.update(&[0; 15]); - mac.update(&[iv]); - mac.update(data); - - mac.finalize().into_bytes() + eax.finish(tag) } } diff --git a/eax/src/stream.rs b/eax/src/stream.rs new file mode 100644 index 00000000..8eb35e21 --- /dev/null +++ b/eax/src/stream.rs @@ -0,0 +1,322 @@ +//! Streaming variant of the EAX mode. +//! +//! # Authentication +//! Due to *AE* (authenticated encryption) nature of EAX, it is vital to verify +//! that both public (also called *associated*) and privacy-protected +//! (encrypted) data has not been tampered with. +//! +//! Because of this, it is required for the consumers to explicitly call +//! [`finish`] after the encryption/decryption operation is complete. +//! This will either return a *tag* (when encrypting) used to authenticate data +//! or a `Result` (when decrypting) that signifies whether the data is authentic, +//! which is when the resulting tag is equal to the one created during encryption. +//! # Panic +//! If the `EaxStream` value will not be consumed via [`finish`] the +//! process will abort, when compiled with `std` feature enabled, to prevent +//! against bugs related to decrypting data without verifying its authenticity. +//! +//! ## Example +//! ``` +//! use eax::{Error, stream::{EaxStream, Decrypt, Encrypt}}; +//! use aes::Aes256; +//! use block_cipher::generic_array::GenericArray; +//! +//! let key = GenericArray::from_slice(b"an example very very secret key."); +//! let nonce = GenericArray::from_slice(b"my unique nonces"); // 128-bits; unique per message +//! let assoc = b"my associated data"; +//! let plaintext = b"plaintext message"; +//! let mut buffer: [u8; 17] = *plaintext; +//! +//!// Encrypt a simple message +//! let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +//! cipher.update_assoc(&assoc[..]); +//! cipher.encrypt(&mut buffer[..9]); +//! cipher.encrypt(&mut buffer[9..]); +//! let tag = cipher.finish(); +//! +//! assert_ne!(buffer, *plaintext); +//! +//! let mut cloned = buffer; +//! +//! // Now decrypt it, using the same key and nonce +//! let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +//! cipher.update_assoc(&assoc[..]); +//! cipher.decrypt(&mut buffer[..5]); +//! cipher.decrypt(&mut buffer[5..10]); +//! cipher.decrypt(&mut buffer[10..]); +//! let res = cipher.finish(&tag); +//! +//! assert_eq!(res, Ok(())); +//! assert_eq!(buffer, *plaintext); +//! +//! // Decrypting the ciphertext with tampered associated data should fail +//! let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +//! cipher.update_assoc(b"tampered"); +//! cipher.decrypt(&mut cloned); +//! let res = cipher.finish(&tag); +//! +//! assert_eq!(res, Err(Error)); +//! ``` +//! [`Eax`]: struct.Eax.html +//! [`Decrypt`]: struct.Decrypt.html +//! [`finish`]: #method.finish + +use crate::*; + +use aead::Nonce; + +use core::marker::PhantomData; +use core::mem; + +/// Auto trait denoting whether the EAX stream is used for encryption/decryption. +pub trait CipherOp {} +/// EAX stream is used in encryption mode. +pub struct Encrypt; +impl CipherOp for Encrypt {} +/// EAX stream is used in decryption mode. +pub struct Decrypt; +impl CipherOp for Decrypt {} + +/// EAX: generic over an underlying block cipher implementation. +/// +/// This type is generic to support substituting alternative cipher +/// implementations. +/// +/// NOTE: This type, in contrast to [`Eax`], can be used in a streaming fashion +/// and operates in-place. +/// +/// # Authentication +/// Due to *AE* (authenticated encryption) nature of EAX, it is vital to verify +/// that both public (also called *associated*) and privacy-protected +/// (encrypted) data has not been tampered with. +/// +/// Because of this, it is required for the consumers to explicitly call +/// [`finish`] after the encryption/decryption operation is complete. +/// This will either return a *tag* (when encrypting) used to authenticate data +/// or a `Result` (when decrypting) that signifies whether the data is authentic, +/// which is when the resulting tag is equal to the one created during encryption. +/// +/// # Panic +/// If the `EaxStream` value will not be consumed via [`finish`] the +/// process will abort, when compiled with `std` feature enabled, to prevent +/// against bugs related to decrypting data without verifying its authenticity. +/// ## Example +/// ``` +/// use eax::{Error, stream::{EaxStream, Decrypt, Encrypt}}; +/// use aes::Aes256; +/// use block_cipher::generic_array::GenericArray; +/// +/// let key = GenericArray::from_slice(b"an example very very secret key."); +/// +/// let nonce = GenericArray::from_slice(b"my unique nonces"); // 128-bits; unique per message +/// +/// let assoc = b"my associated data"; +/// let plaintext = b"plaintext message"; +/// +/// let mut buffer: [u8; 17] = *plaintext; +/// +/// // Encrypt a simple message +/// let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +/// cipher.update_assoc(&assoc[..]); +/// cipher.encrypt(&mut buffer[..9]); +/// cipher.encrypt(&mut buffer[9..]); +/// let tag = cipher.finish(); +/// +/// assert_ne!(buffer, *plaintext); +/// +/// let mut cloned = buffer; +/// +/// // Now decrypt it, using the same key and nonce +/// let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +/// cipher.update_assoc(&assoc[..]); +/// cipher.decrypt(&mut buffer[..5]); +/// cipher.decrypt(&mut buffer[5..10]); +/// cipher.decrypt(&mut buffer[10..]); +/// let res = cipher.finish(&tag); +/// +/// assert_eq!(res, Ok(())); +/// assert_eq!(buffer, *plaintext); +/// +/// // Decrypting the ciphertext with tampered associated data should fail +/// let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +/// +/// cipher.update_assoc(b"tampered"); +/// cipher.decrypt(&mut cloned); +/// let res = cipher.finish(&tag); +/// +/// assert_eq!(res, Err(Error)); +/// ``` +/// +/// [`Eax`]: struct.Eax.html +/// [`Decrypt`]: struct.Decrypt.html +/// [`finish`]: #method.finish +pub struct EaxStream +where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, + Op: CipherOp, +{ + nonce: Nonce, + data: Cmac, + message: Cmac, + ctr: ctr::Ctr128, + /// Denotes whether this stream is used for encryption or decryption. + marker: PhantomData, + /// Verifies at run-time whether the type has been properly consumed via + /// `EaxStream::finish`, otherwise aborts. + bomb: DropBomb, +} + +/// Runtime-enforced linear-ish type. +/// +/// This type is useful to enforce that a value is correctly explicitly consumed. +/// Otherwise, this will abort (only when `std` feature is enabled). +struct DropBomb; +#[cfg(feature = "std")] +impl Drop for DropBomb { + fn drop(&mut self) { + std::eprintln!("Drop bomb says buh-bye"); + std::process::abort(); + } +} + +impl DropBomb { + fn defuse(self) { + mem::forget(self); + } +} + +impl EaxStream +where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, + Op: CipherOp, +{ + /// Creates a stateful EAX instance that is capable of processing both + /// the associated data and the plaintext in an "on-line" fashion. + pub fn with_key_and_nonce( + key: &Key, + nonce: &GenericArray, + ) -> Self { + let prepend_cmac = |key, init_val, data| { + let mut cmac = Cmac::::new(key); + cmac.update(&[0; 15]); + cmac.update(&[init_val]); + cmac.update(data); + cmac + }; + + // https://crypto.stackexchange.com/questions/26948/eax-cipher-mode-with-nonce-equal-header + // has an explanation of eax. + + // l = block cipher size = 128 (for AES-128) = 16 byte + // 1. n ← OMAC(0 || Nonce) + // (the 0 means the number zero in l bits) + let n = prepend_cmac(&key, 0, nonce); + let n = n.finalize().into_bytes(); + + // NOTE: These can be updated online later + // 2. h ← OMAC(1 || associated data) + let h = prepend_cmac(&key, 1, &[]); + // 3. c ← OMAC(2 || enc) + let c = prepend_cmac(&key, 2, &[]); + + let cipher = ctr::Ctr128::::from_block_cipher(Cipher::new(&key), &n); + + EaxStream { + nonce: n, + data: h, + message: c, + ctr: cipher, + marker: PhantomData, + bomb: DropBomb, + } + } +} + +impl EaxStream +where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, + Op: CipherOp, +{ + /// Process the associated data (AD). + #[inline] + pub fn update_assoc(&mut self, aad: &[u8]) { + self.data.update(aad); + } + + /// Derives the tag from the encrypted/decrypted message so far. + /// + /// NOTE: This has to be called when the value is consumed. + #[inline] + fn finish_inner(self) -> Tag { + self.bomb.defuse(); + + let h = self.data.finalize().into_bytes(); + let c = self.message.finalize().into_bytes(); + + self.nonce.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b) + } + + /// Derives the tag from the encrypted/decrypted message so far. + // + /// Prefer using `EaxStream::tag` if `EaxStream` value will not be needed anymore. + #[inline] + pub fn tag_clone(&self) -> Tag { + let h = self.data.clone().finalize().into_bytes(); + let c = self.message.clone().finalize().into_bytes(); + + self.nonce.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b) + } +} + +impl EaxStream +where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, +{ + /// Applies encryption to the plaintext. + #[inline] + pub fn encrypt(&mut self, msg: &mut [u8]) { + self.ctr.apply_keystream(msg); + self.message.update(msg); + } + + /// Derives the tag from the encrypted/decrypted message so far. + #[must_use = "tag must be saved to later verify decrypted data"] + #[inline] + pub fn finish(self) -> Tag { + self.finish_inner() + } +} + +impl EaxStream +where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, +{ + /// Applies decryption to the ciphertext. + #[inline] + pub fn decrypt(&mut self, msg: &mut [u8]) { + self.message.update(msg); + self.ctr.apply_keystream(msg); + } + + /// Finishes the decryption stream, verifying whether the associated and + /// decrypted data stream has not been tampered with. + /// + /// This *has* to be called after every stream decryption operation. + #[must_use = "decrypted data stream must be verified for authenticity"] + pub fn finish(self, expected: &Tag) -> Result<(), Error> { + // Check mac using secure comparison + use subtle::ConstantTimeEq; + + let resulting_tag = &self.finish_inner()[..expected.len()]; + if resulting_tag.ct_eq(expected).unwrap_u8() == 1 { + Ok(()) + } else { + Err(Error) + } + } +} From 1009dcaddc5814be21348d7412271cd3a0b66d57 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Wed, 9 Sep 2020 20:26:04 +0200 Subject: [PATCH 2/7] eax: Import Nonce --- eax/src/lib.rs | 6 +++--- eax/src/stream.rs | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/eax/src/lib.rs b/eax/src/lib.rs index 7ff23df6..2aa2b126 100644 --- a/eax/src/lib.rs +++ b/eax/src/lib.rs @@ -81,7 +81,7 @@ #[cfg(feature = "std")] extern crate std; -pub use aead::{self, AeadInPlace, Error, NewAead}; +pub use aead::{self, AeadInPlace, Error, NewAead, Nonce}; use block_cipher::{ consts::{U0, U16}, @@ -147,7 +147,7 @@ where fn encrypt_in_place_detached( &self, - nonce: &GenericArray, + nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], ) -> Result { @@ -167,7 +167,7 @@ where fn decrypt_in_place_detached( &self, - nonce: &GenericArray, + nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8], tag: &Tag, diff --git a/eax/src/stream.rs b/eax/src/stream.rs index 8eb35e21..32eb4338 100644 --- a/eax/src/stream.rs +++ b/eax/src/stream.rs @@ -63,8 +63,6 @@ use crate::*; -use aead::Nonce; - use core::marker::PhantomData; use core::mem; @@ -194,10 +192,7 @@ where { /// Creates a stateful EAX instance that is capable of processing both /// the associated data and the plaintext in an "on-line" fashion. - pub fn with_key_and_nonce( - key: &Key, - nonce: &GenericArray, - ) -> Self { + pub fn with_key_and_nonce(key: &Key, nonce: &Nonce) -> Self { let prepend_cmac = |key, init_val, data| { let mut cmac = Cmac::::new(key); cmac.update(&[0; 15]); From 505a367ebd0485a2ac9db93c2fc01f426b3c7300 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 14 Sep 2020 11:32:42 +0200 Subject: [PATCH 3/7] eax: Prefer using online terminology rather than stream Not to conflict with the generic STREAM scheme introduced in https://web.cs.ucdavis.edu/~rogaway/papers/oae.pdf. --- eax/src/lib.rs | 10 ++--- eax/src/{stream.rs => online.rs} | 67 ++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 34 deletions(-) rename eax/src/{stream.rs => online.rs} (82%) diff --git a/eax/src/lib.rs b/eax/src/lib.rs index 2aa2b126..06cd6a9c 100644 --- a/eax/src/lib.rs +++ b/eax/src/lib.rs @@ -106,7 +106,7 @@ pub const C_MAX: u64 = (1 << 36) + 16; /// EAX tags pub type Tag = GenericArray; -pub mod stream; +pub mod online; /// EAX: generic over an underlying block cipher implementation. /// @@ -151,13 +151,13 @@ where associated_data: &[u8], buffer: &mut [u8], ) -> Result { - use stream::{EaxStream, Encrypt}; + use online::{EaxOnline, Encrypt}; if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX { return Err(Error); } - let mut eax = EaxStream::::with_key_and_nonce(&self.key, nonce); + let mut eax = EaxOnline::::with_key_and_nonce(&self.key, nonce); eax.update_assoc(associated_data); eax.encrypt(buffer); @@ -172,13 +172,13 @@ where buffer: &mut [u8], tag: &Tag, ) -> Result<(), Error> { - use stream::{Decrypt, EaxStream}; + use online::{Decrypt, EaxOnline}; if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX { return Err(Error); } - let mut eax = EaxStream::::with_key_and_nonce(&self.key, nonce); + let mut eax = EaxOnline::::with_key_and_nonce(&self.key, nonce); eax.update_assoc(associated_data); eax.decrypt(buffer); diff --git a/eax/src/stream.rs b/eax/src/online.rs similarity index 82% rename from eax/src/stream.rs rename to eax/src/online.rs index 32eb4338..e394ce6e 100644 --- a/eax/src/stream.rs +++ b/eax/src/online.rs @@ -1,4 +1,4 @@ -//! Streaming variant of the EAX mode. +//! Online[1] variant of the EAX mode. //! //! # Authentication //! Due to *AE* (authenticated encryption) nature of EAX, it is vital to verify @@ -11,13 +11,13 @@ //! or a `Result` (when decrypting) that signifies whether the data is authentic, //! which is when the resulting tag is equal to the one created during encryption. //! # Panic -//! If the `EaxStream` value will not be consumed via [`finish`] the +//! If the `Eax` value will not be consumed via [`finish`] the //! process will abort, when compiled with `std` feature enabled, to prevent //! against bugs related to decrypting data without verifying its authenticity. //! //! ## Example //! ``` -//! use eax::{Error, stream::{EaxStream, Decrypt, Encrypt}}; +//! use eax::{Error, online::{Eax, Decrypt, Encrypt}}; //! use aes::Aes256; //! use block_cipher::generic_array::GenericArray; //! @@ -28,7 +28,7 @@ //! let mut buffer: [u8; 17] = *plaintext; //! //!// Encrypt a simple message -//! let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +//! let mut cipher = Eax::::with_key_and_nonce(key, nonce); //! cipher.update_assoc(&assoc[..]); //! cipher.encrypt(&mut buffer[..9]); //! cipher.encrypt(&mut buffer[9..]); @@ -39,7 +39,7 @@ //! let mut cloned = buffer; //! //! // Now decrypt it, using the same key and nonce -//! let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +//! let mut cipher = Eax::::with_key_and_nonce(key, nonce); //! cipher.update_assoc(&assoc[..]); //! cipher.decrypt(&mut buffer[..5]); //! cipher.decrypt(&mut buffer[5..10]); @@ -50,13 +50,14 @@ //! assert_eq!(buffer, *plaintext); //! //! // Decrypting the ciphertext with tampered associated data should fail -//! let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +//! let mut cipher = Eax::::with_key_and_nonce(key, nonce); //! cipher.update_assoc(b"tampered"); //! cipher.decrypt(&mut cloned); //! let res = cipher.finish(&tag); //! //! assert_eq!(res, Err(Error)); //! ``` +//! [1]: https://en.wikipedia.org/wiki/Online_algorithm //! [`Eax`]: struct.Eax.html //! [`Decrypt`]: struct.Decrypt.html //! [`finish`]: #method.finish @@ -66,22 +67,24 @@ use crate::*; use core::marker::PhantomData; use core::mem; -/// Auto trait denoting whether the EAX stream is used for encryption/decryption. +pub use Eax as EaxOnline; + +/// Marker trait denoting whether the EAX stream is used for encryption/decryption. pub trait CipherOp {} -/// EAX stream is used in encryption mode. +/// Marker struct for EAX stream used in encryption mode. pub struct Encrypt; impl CipherOp for Encrypt {} -/// EAX stream is used in decryption mode. +/// Marker struct for EAX stream used in decryption mode. pub struct Decrypt; impl CipherOp for Decrypt {} -/// EAX: generic over an underlying block cipher implementation. +/// Online[1] variant of the EAX mode. /// /// This type is generic to support substituting alternative cipher /// implementations. /// -/// NOTE: This type, in contrast to [`Eax`], can be used in a streaming fashion -/// and operates in-place. +/// In contrast to [`Eax`], can be used in an online[1] fashion and +/// operates in-place. /// /// # Authentication /// Due to *AE* (authenticated encryption) nature of EAX, it is vital to verify @@ -95,12 +98,12 @@ impl CipherOp for Decrypt {} /// which is when the resulting tag is equal to the one created during encryption. /// /// # Panic -/// If the `EaxStream` value will not be consumed via [`finish`] the +/// If the `Eax` value will not be consumed via [`finish`] the /// process will abort, when compiled with `std` feature enabled, to prevent /// against bugs related to decrypting data without verifying its authenticity. /// ## Example /// ``` -/// use eax::{Error, stream::{EaxStream, Decrypt, Encrypt}}; +/// use eax::{Error, online::{Eax, Decrypt, Encrypt}}; /// use aes::Aes256; /// use block_cipher::generic_array::GenericArray; /// @@ -114,7 +117,7 @@ impl CipherOp for Decrypt {} /// let mut buffer: [u8; 17] = *plaintext; /// /// // Encrypt a simple message -/// let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +/// let mut cipher = Eax::::with_key_and_nonce(key, nonce); /// cipher.update_assoc(&assoc[..]); /// cipher.encrypt(&mut buffer[..9]); /// cipher.encrypt(&mut buffer[9..]); @@ -125,7 +128,7 @@ impl CipherOp for Decrypt {} /// let mut cloned = buffer; /// /// // Now decrypt it, using the same key and nonce -/// let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +/// let mut cipher = Eax::::with_key_and_nonce(key, nonce); /// cipher.update_assoc(&assoc[..]); /// cipher.decrypt(&mut buffer[..5]); /// cipher.decrypt(&mut buffer[5..10]); @@ -136,7 +139,7 @@ impl CipherOp for Decrypt {} /// assert_eq!(buffer, *plaintext); /// /// // Decrypting the ciphertext with tampered associated data should fail -/// let mut cipher = EaxStream::::with_key_and_nonce(key, nonce); +/// let mut cipher = Eax::::with_key_and_nonce(key, nonce); /// /// cipher.update_assoc(b"tampered"); /// cipher.decrypt(&mut cloned); @@ -145,10 +148,11 @@ impl CipherOp for Decrypt {} /// assert_eq!(res, Err(Error)); /// ``` /// -/// [`Eax`]: struct.Eax.html +/// [1]: https://en.wikipedia.org/wiki/Online_algorithm +/// [`Eax`]: ../struct.Eax.html /// [`Decrypt`]: struct.Decrypt.html /// [`finish`]: #method.finish -pub struct EaxStream +pub struct Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, @@ -161,7 +165,7 @@ where /// Denotes whether this stream is used for encryption or decryption. marker: PhantomData, /// Verifies at run-time whether the type has been properly consumed via - /// `EaxStream::finish`, otherwise aborts. + /// `Eax::finish`, otherwise aborts. bomb: DropBomb, } @@ -184,7 +188,7 @@ impl DropBomb { } } -impl EaxStream +impl Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, @@ -218,7 +222,7 @@ where let cipher = ctr::Ctr128::::from_block_cipher(Cipher::new(&key), &n); - EaxStream { + Eax { nonce: n, data: h, message: c, @@ -229,7 +233,7 @@ where } } -impl EaxStream +impl Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, @@ -255,8 +259,11 @@ where } /// Derives the tag from the encrypted/decrypted message so far. - // - /// Prefer using `EaxStream::tag` if `EaxStream` value will not be needed anymore. + /// + /// If the encryption/decryption operation is finished, [`finish`] method + /// *must* be called instead. + /// + ///[`finish`]: #method.finish #[inline] pub fn tag_clone(&self) -> Tag { let h = self.data.clone().finalize().into_bytes(); @@ -266,7 +273,7 @@ where } } -impl EaxStream +impl Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, @@ -278,7 +285,9 @@ where self.message.update(msg); } - /// Derives the tag from the encrypted/decrypted message so far. + /// Finishes the encryption stream, returning the derived tag. + /// + /// This *must* be called after the stream encryption is finished. #[must_use = "tag must be saved to later verify decrypted data"] #[inline] pub fn finish(self) -> Tag { @@ -286,7 +295,7 @@ where } } -impl EaxStream +impl Eax where Cipher: BlockCipher + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength>, @@ -301,7 +310,7 @@ where /// Finishes the decryption stream, verifying whether the associated and /// decrypted data stream has not been tampered with. /// - /// This *has* to be called after every stream decryption operation. + /// This *must* be called after the stream decryption is finished. #[must_use = "decrypted data stream must be verified for authenticity"] pub fn finish(self, expected: &Tag) -> Result<(), Error> { // Check mac using secure comparison From 2a31050cbb33726a09910f0a8560cb421bb7dccc Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Thu, 17 Sep 2020 22:03:37 +0200 Subject: [PATCH 4/7] eax: Drop the drop bomb code --- eax/Cargo.toml | 1 - eax/src/lib.rs | 3 --- eax/src/online.rs | 34 ---------------------------------- 3 files changed, 38 deletions(-) diff --git a/eax/Cargo.toml b/eax/Cargo.toml index e18193e6..14e997cd 100644 --- a/eax/Cargo.toml +++ b/eax/Cargo.toml @@ -31,7 +31,6 @@ aead = { version = "0.3", features = ["dev"], default-features = false } [features] default = ["alloc"] -std = ["aead/std"] alloc = ["aead/alloc"] heapless = ["aead/heapless"] diff --git a/eax/src/lib.rs b/eax/src/lib.rs index 06cd6a9c..a32e5e64 100644 --- a/eax/src/lib.rs +++ b/eax/src/lib.rs @@ -78,9 +78,6 @@ #![deny(unsafe_code)] #![warn(missing_docs, rust_2018_idioms)] -#[cfg(feature = "std")] -extern crate std; - pub use aead::{self, AeadInPlace, Error, NewAead, Nonce}; use block_cipher::{ diff --git a/eax/src/online.rs b/eax/src/online.rs index e394ce6e..adb8661c 100644 --- a/eax/src/online.rs +++ b/eax/src/online.rs @@ -10,10 +10,6 @@ //! This will either return a *tag* (when encrypting) used to authenticate data //! or a `Result` (when decrypting) that signifies whether the data is authentic, //! which is when the resulting tag is equal to the one created during encryption. -//! # Panic -//! If the `Eax` value will not be consumed via [`finish`] the -//! process will abort, when compiled with `std` feature enabled, to prevent -//! against bugs related to decrypting data without verifying its authenticity. //! //! ## Example //! ``` @@ -65,7 +61,6 @@ use crate::*; use core::marker::PhantomData; -use core::mem; pub use Eax as EaxOnline; @@ -97,10 +92,6 @@ impl CipherOp for Decrypt {} /// or a `Result` (when decrypting) that signifies whether the data is authentic, /// which is when the resulting tag is equal to the one created during encryption. /// -/// # Panic -/// If the `Eax` value will not be consumed via [`finish`] the -/// process will abort, when compiled with `std` feature enabled, to prevent -/// against bugs related to decrypting data without verifying its authenticity. /// ## Example /// ``` /// use eax::{Error, online::{Eax, Decrypt, Encrypt}}; @@ -164,28 +155,6 @@ where ctr: ctr::Ctr128, /// Denotes whether this stream is used for encryption or decryption. marker: PhantomData, - /// Verifies at run-time whether the type has been properly consumed via - /// `Eax::finish`, otherwise aborts. - bomb: DropBomb, -} - -/// Runtime-enforced linear-ish type. -/// -/// This type is useful to enforce that a value is correctly explicitly consumed. -/// Otherwise, this will abort (only when `std` feature is enabled). -struct DropBomb; -#[cfg(feature = "std")] -impl Drop for DropBomb { - fn drop(&mut self) { - std::eprintln!("Drop bomb says buh-bye"); - std::process::abort(); - } -} - -impl DropBomb { - fn defuse(self) { - mem::forget(self); - } } impl Eax @@ -228,7 +197,6 @@ where message: c, ctr: cipher, marker: PhantomData, - bomb: DropBomb, } } } @@ -250,8 +218,6 @@ where /// NOTE: This has to be called when the value is consumed. #[inline] fn finish_inner(self) -> Tag { - self.bomb.defuse(); - let h = self.data.finalize().into_bytes(); let c = self.message.finalize().into_bytes(); From 0d287bc06c2f5945e25f69749a1833370db6b578 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Thu, 17 Sep 2020 22:06:16 +0200 Subject: [PATCH 5/7] eax: Authenticate before decrypting in lib.rs, as before Brings back the old behavior - it's harder to misuse but separates the implementations of online and offline variants. --- eax/src/lib.rs | 77 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/eax/src/lib.rs b/eax/src/lib.rs index a32e5e64..84105d4b 100644 --- a/eax/src/lib.rs +++ b/eax/src/lib.rs @@ -148,18 +148,31 @@ where associated_data: &[u8], buffer: &mut [u8], ) -> Result { - use online::{EaxOnline, Encrypt}; - if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX { return Err(Error); } - let mut eax = EaxOnline::::with_key_and_nonce(&self.key, nonce); + // https://crypto.stackexchange.com/questions/26948/eax-cipher-mode-with-nonce-equal-header + // has an explanation of eax. + + // l = block cipher size = 128 (for AES-128) = 16 byte + // 1. n ← OMAC(0 || Nonce) + // (the 0 means the number zero in l bits) + let n = Self::cmac_with_iv(&self.key, 0, nonce); + + // 2. h ← OMAC(1 || associated data) + let h = Self::cmac_with_iv(&self.key, 1, associated_data); + + // 3. enc ← CTR(M) using n as iv + let mut cipher = ctr::Ctr128::::from_block_cipher(Cipher::new(&self.key), &n); + cipher.apply_keystream(buffer); - eax.update_assoc(associated_data); - eax.encrypt(buffer); + // 4. c ← OMAC(2 || enc) + let c = Self::cmac_with_iv(&self.key, 2, buffer); - Ok(eax.finish()) + // 5. tag ← n ^ h ^ c + // (^ means xor) + Ok(n.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b)) } fn decrypt_in_place_detached( @@ -169,17 +182,57 @@ where buffer: &mut [u8], tag: &Tag, ) -> Result<(), Error> { - use online::{Decrypt, EaxOnline}; - if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX { return Err(Error); } - let mut eax = EaxOnline::::with_key_and_nonce(&self.key, nonce); + // 1. n ← OMAC(0 || Nonce) + let n = Self::cmac_with_iv(&self.key, 0, nonce); + + // 2. h ← OMAC(1 || associated data) + let h = Self::cmac_with_iv(&self.key, 1, associated_data); + + // 4. c ← OMAC(2 || enc) + let c = Self::cmac_with_iv(&self.key, 2, buffer); + + // 5. tag ← n ^ h ^ c + // (^ means xor) + let expected_tag = n.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b); - eax.update_assoc(associated_data); - eax.decrypt(buffer); + let expected_tag = &expected_tag[..tag.len()]; - eax.finish(tag) + // Check mac using secure comparison + use subtle::ConstantTimeEq; + if expected_tag.ct_eq(tag).unwrap_u8() == 1 { + // Decrypt + let mut cipher = ctr::Ctr128::::from_block_cipher(Cipher::new(&self.key), &n); + cipher.apply_keystream(buffer); + Ok(()) + } else { + Err(Error) + } + } +} + +impl Eax +where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, +{ + /// CMAC/OMAC1 + /// + /// To avoid constructing new buffers on the heap, an iv encoded into 16 + /// bytes is prepended inside this function. + fn cmac_with_iv( + key: &GenericArray, + iv: u8, + data: &[u8], + ) -> GenericArray as Mac>::OutputSize> { + let mut mac = Cmac::::new(key); + mac.update(&[0; 15]); + mac.update(&[iv]); + mac.update(data); + + mac.finalize().into_bytes() } } From 63e0b6c12c351a3ddc016ae0a0ea6c10120fb0a8 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Thu, 17 Sep 2020 23:09:55 +0200 Subject: [PATCH 6/7] WIP: eax: Separate internal online impl for testing --- eax/src/online.rs | 282 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 211 insertions(+), 71 deletions(-) diff --git a/eax/src/online.rs b/eax/src/online.rs index adb8661c..34cc90d7 100644 --- a/eax/src/online.rs +++ b/eax/src/online.rs @@ -37,9 +37,9 @@ //! // Now decrypt it, using the same key and nonce //! let mut cipher = Eax::::with_key_and_nonce(key, nonce); //! cipher.update_assoc(&assoc[..]); -//! cipher.decrypt(&mut buffer[..5]); -//! cipher.decrypt(&mut buffer[5..10]); -//! cipher.decrypt(&mut buffer[10..]); +//! cipher.decrypt_unauthenticated(&mut buffer[..5]); +//! cipher.decrypt_unauthenticated(&mut buffer[5..10]); +//! cipher.decrypt_unauthenticated(&mut buffer[10..]); //! let res = cipher.finish(&tag); //! //! assert_eq!(res, Ok(())); @@ -48,7 +48,7 @@ //! // Decrypting the ciphertext with tampered associated data should fail //! let mut cipher = Eax::::with_key_and_nonce(key, nonce); //! cipher.update_assoc(b"tampered"); -//! cipher.decrypt(&mut cloned); +//! cipher.decrypt_unauthenticated(&mut cloned); //! let res = cipher.finish(&tag); //! //! assert_eq!(res, Err(Error)); @@ -121,9 +121,9 @@ impl CipherOp for Decrypt {} /// // Now decrypt it, using the same key and nonce /// let mut cipher = Eax::::with_key_and_nonce(key, nonce); /// cipher.update_assoc(&assoc[..]); -/// cipher.decrypt(&mut buffer[..5]); -/// cipher.decrypt(&mut buffer[5..10]); -/// cipher.decrypt(&mut buffer[10..]); +/// cipher.decrypt_unauthenticated(&mut buffer[..5]); +/// cipher.decrypt_unauthenticated(&mut buffer[5..10]); +/// cipher.decrypt_unauthenticated(&mut buffer[10..]); /// let res = cipher.finish(&tag); /// /// assert_eq!(res, Ok(())); @@ -133,7 +133,7 @@ impl CipherOp for Decrypt {} /// let mut cipher = Eax::::with_key_and_nonce(key, nonce); /// /// cipher.update_assoc(b"tampered"); -/// cipher.decrypt(&mut cloned); +/// cipher.decrypt_unauthenticated(&mut cloned); /// let res = cipher.finish(&tag); /// /// assert_eq!(res, Err(Error)); @@ -149,10 +149,7 @@ where Cipher::ParBlocks: ArrayLength>, Op: CipherOp, { - nonce: Nonce, - data: Cmac, - message: Cmac, - ctr: ctr::Ctr128, + imp: EaxImpl, /// Denotes whether this stream is used for encryption or decryption. marker: PhantomData, } @@ -166,6 +163,107 @@ where /// Creates a stateful EAX instance that is capable of processing both /// the associated data and the plaintext in an "on-line" fashion. pub fn with_key_and_nonce(key: &Key, nonce: &Nonce) -> Self { + let imp = EaxImpl::::with_key_and_nonce(key, nonce); + + Self { + imp, + marker: PhantomData, + } + } + + /// Process the associated data (AD). + #[inline] + pub fn update_assoc(&mut self, aad: &[u8]) { + self.imp.update_assoc(aad); + } + + /// Derives the tag from the encrypted/decrypted message so far. + /// + /// If the encryption/decryption operation is finished, [`finish`] method + /// *must* be called instead. + /// + ///[`finish`]: #method.finish + #[inline] + pub fn tag_clone(&self) -> Tag { + self.imp.tag_clone() + } +} + +impl Eax +where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, +{ + /// Applies encryption to the plaintext. + #[inline] + pub fn encrypt(&mut self, msg: &mut [u8]) { + self.imp.encrypt(msg) + } + + /// Finishes the encryption stream, returning the derived tag. + /// + /// This *must* be called after the stream encryption is finished. + #[must_use = "tag must be saved to later verify decrypted data"] + #[inline] + pub fn finish(self) -> Tag { + self.imp.tag() + } +} + +impl Eax +where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, +{ + /// Applies decryption to the ciphertext **without** verifying the + /// authenticity of decrypted message. + /// + /// To correctly verify the authenticity, use the [`finish`] associated + /// function. + /// + /// [`finish`]: #method.finish + #[inline] + pub fn decrypt_unauthenticated(&mut self, msg: &mut [u8]) { + self.imp.decrypt(msg) + } + + /// Finishes the decryption stream, verifying whether the associated and + /// decrypted data stream has not been tampered with. + /// + /// This *must* be called after the stream decryption is finished. + #[must_use = "decrypted data stream must be verified for authenticity"] + pub fn finish(self, expected: &Tag) -> Result<(), Error> { + self.imp.verify_ct(expected) + } +} + +/// Implementation of the raw EAX operations. +/// +/// Main reason behind extracting the logic to a single, separate type is to +/// facilitate testing of the internal logic. +#[doc(hidden)] +struct EaxImpl +where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, +{ + nonce: Nonce, + data: Cmac, + message: Cmac, + ctr: ctr::Ctr128, + // HACK: Needed for the test harness due to AEAD trait online/offline interface mismatch + #[cfg(test)] + key: Key, +} + +impl EaxImpl +where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, +{ + /// Creates a stateful EAX instance that is capable of processing both + /// the associated data and the plaintext in an "on-line" fashion. + fn with_key_and_nonce(key: &Key, nonce: &Nonce) -> Self { let prepend_cmac = |key, init_val, data| { let mut cmac = Cmac::::new(key); cmac.update(&[0; 15]); @@ -191,33 +289,39 @@ where let cipher = ctr::Ctr128::::from_block_cipher(Cipher::new(&key), &n); - Eax { + Self { nonce: n, data: h, message: c, ctr: cipher, - marker: PhantomData, + #[cfg(test)] + key: key.clone(), } } -} -impl Eax -where - Cipher: BlockCipher + NewBlockCipher + Clone, - Cipher::ParBlocks: ArrayLength>, - Op: CipherOp, -{ /// Process the associated data (AD). #[inline] pub fn update_assoc(&mut self, aad: &[u8]) { self.data.update(aad); } + /// Applies encryption to the plaintext. + #[inline] + fn encrypt(&mut self, msg: &mut [u8]) { + self.ctr.apply_keystream(msg); + self.message.update(msg); + } + + /// Applies decryption to the ciphertext. + #[inline] + fn decrypt(&mut self, msg: &mut [u8]) { + self.message.update(msg); + self.ctr.apply_keystream(msg); + } + /// Derives the tag from the encrypted/decrypted message so far. - /// - /// NOTE: This has to be called when the value is consumed. #[inline] - fn finish_inner(self) -> Tag { + fn tag(self) -> Tag { let h = self.data.finalize().into_bytes(); let c = self.message.finalize().into_bytes(); @@ -225,64 +329,21 @@ where } /// Derives the tag from the encrypted/decrypted message so far. - /// - /// If the encryption/decryption operation is finished, [`finish`] method - /// *must* be called instead. - /// - ///[`finish`]: #method.finish #[inline] - pub fn tag_clone(&self) -> Tag { + fn tag_clone(&self) -> Tag { let h = self.data.clone().finalize().into_bytes(); let c = self.message.clone().finalize().into_bytes(); self.nonce.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b) } -} - -impl Eax -where - Cipher: BlockCipher + NewBlockCipher + Clone, - Cipher::ParBlocks: ArrayLength>, -{ - /// Applies encryption to the plaintext. - #[inline] - pub fn encrypt(&mut self, msg: &mut [u8]) { - self.ctr.apply_keystream(msg); - self.message.update(msg); - } - - /// Finishes the encryption stream, returning the derived tag. - /// - /// This *must* be called after the stream encryption is finished. - #[must_use = "tag must be saved to later verify decrypted data"] - #[inline] - pub fn finish(self) -> Tag { - self.finish_inner() - } -} - -impl Eax -where - Cipher: BlockCipher + NewBlockCipher + Clone, - Cipher::ParBlocks: ArrayLength>, -{ - /// Applies decryption to the ciphertext. - #[inline] - pub fn decrypt(&mut self, msg: &mut [u8]) { - self.message.update(msg); - self.ctr.apply_keystream(msg); - } /// Finishes the decryption stream, verifying whether the associated and /// decrypted data stream has not been tampered with. - /// - /// This *must* be called after the stream decryption is finished. - #[must_use = "decrypted data stream must be verified for authenticity"] - pub fn finish(self, expected: &Tag) -> Result<(), Error> { - // Check mac using secure comparison + fn verify_ct(self, expected: &Tag) -> Result<(), Error> { + // Check MAC using secure comparison use subtle::ConstantTimeEq; - let resulting_tag = &self.finish_inner()[..expected.len()]; + let resulting_tag = &self.tag()[..expected.len()]; if resulting_tag.ct_eq(expected).unwrap_u8() == 1 { Ok(()) } else { @@ -290,3 +351,82 @@ where } } } + +// Because the current AEAD test harness expects the types to implement both +// `NewAead` and `AeadMutInPlace` traits, do so here so that we can test the +// internal logic used by the public interface for the online EAX variant. +// These are not publicly implemented in general, because the traits are +// designed for offline usage and are somewhat wasteful when used in online mode. +#[cfg(test)] +mod test_impl { + use super::*; + use aead::AeadMutInPlace; + + impl NewAead for EaxImpl + where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, + { + type KeySize = Cipher::KeySize; + + fn new(key: &Key) -> Self { + // HACK: The nonce will be initialized by the appropriate + // decrypt/encrypt functions from `AeadMutInPlace` implementation. + // This is currently done so because that trait only implements + // offline operations and thus need to re-initialize the `EaxImpl` + // instance. + let nonce = GenericArray::default(); + + Self::with_key_and_nonce(key, &nonce) + } + } + + impl AeadMutInPlace for super::EaxImpl + where + Cipher: BlockCipher + NewBlockCipher + Clone, + Cipher::ParBlocks: ArrayLength>, + { + type NonceSize = Cipher::BlockSize; + type TagSize = as Mac>::OutputSize; + type CiphertextOverhead = U0; + + fn encrypt_in_place_detached( + &mut self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result { + // HACK: Reinitialize the instance + *self = Self::with_key_and_nonce(&self.key.clone(), nonce); + + self.update_assoc(associated_data); + self.encrypt(buffer); + + Ok(self.tag_clone()) + } + + fn decrypt_in_place_detached( + &mut self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + expected_tag: &Tag, + ) -> Result<(), Error> { + // HACK: Reinitialize the instance + *self = Self::with_key_and_nonce(&self.key.clone(), nonce); + + self.update_assoc(associated_data); + self.decrypt(buffer); + + let tag = self.tag_clone(); + + // Check mac using secure comparison + use subtle::ConstantTimeEq; + if expected_tag.ct_eq(&tag).unwrap_u8() == 1 { + Ok(()) + } else { + Err(Error) + } + } + } +} From 3b180b9c0927e599e8d396ff8683f6b1bfeb05c3 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Sun, 27 Sep 2020 15:13:44 +0200 Subject: [PATCH 7/7] eax: Mention IND-CCA vulnerability for EAX online variant --- eax/src/online.rs | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/eax/src/online.rs b/eax/src/online.rs index 34cc90d7..f21989e0 100644 --- a/eax/src/online.rs +++ b/eax/src/online.rs @@ -37,9 +37,9 @@ //! // Now decrypt it, using the same key and nonce //! let mut cipher = Eax::::with_key_and_nonce(key, nonce); //! cipher.update_assoc(&assoc[..]); -//! cipher.decrypt_unauthenticated(&mut buffer[..5]); -//! cipher.decrypt_unauthenticated(&mut buffer[5..10]); -//! cipher.decrypt_unauthenticated(&mut buffer[10..]); +//! cipher.decrypt_unauthenticated_hazmat(&mut buffer[..5]); +//! cipher.decrypt_unauthenticated_hazmat(&mut buffer[5..10]); +//! cipher.decrypt_unauthenticated_hazmat(&mut buffer[10..]); //! let res = cipher.finish(&tag); //! //! assert_eq!(res, Ok(())); @@ -48,7 +48,7 @@ //! // Decrypting the ciphertext with tampered associated data should fail //! let mut cipher = Eax::::with_key_and_nonce(key, nonce); //! cipher.update_assoc(b"tampered"); -//! cipher.decrypt_unauthenticated(&mut cloned); +//! cipher.decrypt_unauthenticated_hazmat(&mut cloned); //! let res = cipher.finish(&tag); //! //! assert_eq!(res, Err(Error)); @@ -121,9 +121,9 @@ impl CipherOp for Decrypt {} /// // Now decrypt it, using the same key and nonce /// let mut cipher = Eax::::with_key_and_nonce(key, nonce); /// cipher.update_assoc(&assoc[..]); -/// cipher.decrypt_unauthenticated(&mut buffer[..5]); -/// cipher.decrypt_unauthenticated(&mut buffer[5..10]); -/// cipher.decrypt_unauthenticated(&mut buffer[10..]); +/// cipher.decrypt_unauthenticated_hazmat(&mut buffer[..5]); +/// cipher.decrypt_unauthenticated_hazmat(&mut buffer[5..10]); +/// cipher.decrypt_unauthenticated_hazmat(&mut buffer[10..]); /// let res = cipher.finish(&tag); /// /// assert_eq!(res, Ok(())); @@ -133,7 +133,7 @@ impl CipherOp for Decrypt {} /// let mut cipher = Eax::::with_key_and_nonce(key, nonce); /// /// cipher.update_assoc(b"tampered"); -/// cipher.decrypt_unauthenticated(&mut cloned); +/// cipher.decrypt_unauthenticated_hazmat(&mut cloned); /// let res = cipher.finish(&tag); /// /// assert_eq!(res, Err(Error)); @@ -221,9 +221,25 @@ where /// To correctly verify the authenticity, use the [`finish`] associated /// function. /// + /// # ☣️ BEWARE! ☣️ + /// This is a low-level operation that simultaneously decrypts the data and + /// calculates an intermediate tag used to verify the authenticity of the + /// data (used when the online decryption is finished). + /// + /// Because this is exposed solely as a building block operation, an extra + /// care must be taken when using this function. + /// + /// Specifically, when misused this may be vulnerable to a chosen-ciphertext + /// attack (IND-CCA). Due to online nature of this function, the decryption + /// and partial tag calculation is done simultaneously, per chunk. + /// An attacker might choose ciphertexts to be decrypted and, while the + /// final decryption will fail because the attacker can't calculate tag + /// authenticating the message, obtained decryptions may leak information + /// about the decryption scheme (e.g. leaking parts of the secret key). + /// /// [`finish`]: #method.finish #[inline] - pub fn decrypt_unauthenticated(&mut self, msg: &mut [u8]) { + pub fn decrypt_unauthenticated_hazmat(&mut self, msg: &mut [u8]) { self.imp.decrypt(msg) }