Skip to content

Commit

Permalink
eax: Allow variable tag length
Browse files Browse the repository at this point in the history
Implemented analogous to ccm by passing a second generic argument to
Eax.
  • Loading branch information
Flakebi committed Oct 17, 2020
1 parent 9e4d072 commit ce1fdb2
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 34 deletions.
75 changes: 62 additions & 13 deletions eax/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,42 @@
//! # }
//! ```
//!
//! ## Custom Tag Length
//!
//! The tag for eax is usually 16 bytes long but it can be shortened if needed.
//! The second generic argument of `Eax` can be set to the tag length:
//!
//! ```
//! # #[cfg(feature = "heapless")]
//! # {
//! use aes::Aes256;
//! use eax::Eax;
//! use eax::aead::{AeadInPlace, NewAead, generic_array::GenericArray};
//! use eax::aead::heapless::{Vec, consts::U8, consts::U128};
//!
//! let key = GenericArray::from_slice(b"an example very very secret key.");
//! let cipher = Eax::<Aes256, U8>::new(key);
//!
//! let nonce = GenericArray::from_slice(b"my unique nonces"); // 128-bits; unique per message
//!
//! let mut buffer: Vec<u8, U128> = Vec::new();
//! buffer.extend_from_slice(b"plaintext message");
//!
//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
//! let tag = cipher.encrypt_in_place_detached(nonce, b"", &mut buffer).expect("encryption failure!");
//!
//! // The tag has only 8 bytes, compared to the usual 16 bytes
//! assert_eq!(tag.len(), 8);
//!
//! // `buffer` now contains the message ciphertext
//! assert_ne!(&buffer, b"plaintext message");
//!
//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
//! cipher.decrypt_in_place_detached(nonce, b"", &mut buffer, &tag).expect("decryption failure!");
//! assert_eq!(&buffer, b"plaintext message");
//! # }
//! ```
//!
//! [1]: https://en.wikipedia.org/wiki/Authenticated_encryption
#![no_std]
Expand All @@ -78,7 +114,7 @@
#![deny(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms)]

pub use aead::{self, AeadInPlace, Error, NewAead, Nonce};
pub use aead::{self, AeadInPlace, Error, NewAead, Nonce, Tag};
pub use cipher;

use cipher::{
Expand All @@ -88,6 +124,11 @@ use cipher::{
stream::{FromBlockCipher, SyncStreamCipher},
};
use cmac::{crypto_mac::NewMac, Cmac, Mac};
use core::marker::PhantomData;

mod traits;

use traits::TagSize;

// TODO Max values?
/// Maximum length of associated data
Expand All @@ -99,9 +140,6 @@ pub const P_MAX: u64 = 1 << 36;
/// Maximum length of ciphertext
pub const C_MAX: u64 = (1 << 36) + 16;

/// EAX tags
pub type Tag = GenericArray<u8, U16>;

pub mod online;

/// EAX: generic over an underlying block cipher implementation.
Expand All @@ -110,43 +148,51 @@ pub mod online;
/// implementations.
///
/// If in doubt, use the built-in [`Aes128Eax`] and [`Aes256Eax`] type aliases.
///
/// Type parameters:
/// - `Cipher`: block cipher.
/// - `M`: size of MAC tag, valid values: up to `U16`.
#[derive(Clone)]
pub struct Eax<Cipher>
pub struct Eax<Cipher, M = U16>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: ArrayLength<u8> + TagSize,
{
/// Encryption key
key: Key<Cipher>,
_tag_size: PhantomData<M>,
}

impl<Cipher> NewAead for Eax<Cipher>
impl<Cipher, M> NewAead for Eax<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: ArrayLength<u8> + TagSize,
{
type KeySize = Cipher::KeySize;

fn new(key: &Key<Cipher>) -> Self {
Self { key: key.clone() }
Self { key: key.clone(), _tag_size: Default::default() }
}
}

impl<Cipher> AeadInPlace for Eax<Cipher>
impl<Cipher, M> AeadInPlace for Eax<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: ArrayLength<u8> + TagSize,
{
type NonceSize = Cipher::BlockSize;
type TagSize = <Cmac<Cipher> as Mac>::OutputSize;
type TagSize = M;
type CiphertextOverhead = U0;

fn encrypt_in_place_detached(
&self,
nonce: &Nonce<Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
) -> Result<Tag, Error> {
) -> Result<Tag<M>, Error> {
if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
return Err(Error);
}
Expand All @@ -171,15 +217,17 @@ where

// 5. tag ← n ^ h ^ c
// (^ means xor)
Ok(n.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b))
let full_tag = n.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b);
let tag = Tag::<M>::clone_from_slice(&full_tag[..M::to_usize()]);
Ok(tag)
}

fn decrypt_in_place_detached(
&self,
nonce: &Nonce<Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
tag: &Tag,
tag: &Tag<M>,
) -> Result<(), Error> {
if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX {
return Err(Error);
Expand Down Expand Up @@ -213,10 +261,11 @@ where
}
}

impl<Cipher> Eax<Cipher>
impl<Cipher, M> Eax<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: ArrayLength<u8> + TagSize,
{
/// CMAC/OMAC1
///
Expand Down
55 changes: 34 additions & 21 deletions eax/src/online.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,27 +141,29 @@ impl CipherOp for Decrypt {}
/// [`Eax`]: ../struct.Eax.html
/// [`Decrypt`]: struct.Decrypt.html
/// [`finish`]: #method.finish
pub struct Eax<Cipher, Op>
pub struct Eax<Cipher, Op, M = U16>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
Op: CipherOp,
M: ArrayLength<u8> + TagSize,
{
imp: EaxImpl<Cipher>,
imp: EaxImpl<Cipher, M>,
/// Denotes whether this stream is used for encryption or decryption.
marker: PhantomData<Op>,
}

impl<Cipher, Op> Eax<Cipher, Op>
impl<Cipher, Op, M> Eax<Cipher, Op, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
Op: CipherOp,
M: ArrayLength<u8> + TagSize,
{
/// 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<Cipher>, nonce: &Nonce<Cipher::BlockSize>) -> Self {
let imp = EaxImpl::<Cipher>::with_key_and_nonce(key, nonce);
let imp = EaxImpl::<Cipher, M>::with_key_and_nonce(key, nonce);

Self {
imp,
Expand All @@ -182,15 +184,16 @@ where
///
///[`finish`]: #method.finish
#[inline]
pub fn tag_clone(&self) -> Tag {
pub fn tag_clone(&self) -> Tag<M> {
self.imp.tag_clone()
}
}

impl<Cipher> Eax<Cipher, Encrypt>
impl<Cipher, M> Eax<Cipher, Encrypt, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: ArrayLength<u8> + TagSize,
{
/// Applies encryption to the plaintext.
#[inline]
Expand All @@ -203,15 +206,16 @@ where
/// 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 {
pub fn finish(self) -> Tag<M> {
self.imp.tag()
}
}

impl<Cipher> Eax<Cipher, Decrypt>
impl<Cipher, M> Eax<Cipher, Decrypt, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: ArrayLength<u8> + TagSize,
{
/// Applies decryption to the ciphertext **without** verifying the
/// authenticity of decrypted message.
Expand Down Expand Up @@ -246,7 +250,7 @@ where
///
/// 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> {
pub fn finish(self, expected: &Tag<M>) -> Result<(), Error> {
self.imp.verify_ct(expected)
}
}
Expand All @@ -256,10 +260,11 @@ where
/// Main reason behind extracting the logic to a single, separate type is to
/// facilitate testing of the internal logic.
#[doc(hidden)]
struct EaxImpl<Cipher>
struct EaxImpl<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: ArrayLength<u8> + TagSize,
{
nonce: Nonce<Cipher::BlockSize>,
data: Cmac<Cipher>,
Expand All @@ -268,12 +273,14 @@ where
// HACK: Needed for the test harness due to AEAD trait online/offline interface mismatch
#[cfg(test)]
key: Key<Cipher>,
_tag_size: PhantomData<M>,
}

impl<Cipher> EaxImpl<Cipher>
impl<Cipher, M> EaxImpl<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: ArrayLength<u8> + TagSize,
{
/// Creates a stateful EAX instance that is capable of processing both
/// the associated data and the plaintext in an "on-line" fashion.
Expand Down Expand Up @@ -310,6 +317,7 @@ where
ctr: cipher,
#[cfg(test)]
key: key.clone(),
_tag_size: Default::default(),
}
}

Expand All @@ -335,25 +343,28 @@ where

/// Derives the tag from the encrypted/decrypted message so far.
#[inline]
fn tag(self) -> Tag {
fn tag(self) -> Tag<M> {
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)

let full_tag = self.nonce.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b);
Tag::<M>::clone_from_slice(&full_tag[..M::to_usize()])
}

/// Derives the tag from the encrypted/decrypted message so far.
#[inline]
fn tag_clone(&self) -> Tag {
fn tag_clone(&self) -> Tag<M> {
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)
let full_tag = self.nonce.zip(h, |a, b| a ^ b).zip(c, |a, b| a ^ b);
Tag::<M>::clone_from_slice(&full_tag[..M::to_usize()])
}

/// Finishes the decryption stream, verifying whether the associated and
/// decrypted data stream has not been tampered with.
fn verify_ct(self, expected: &Tag) -> Result<(), Error> {
fn verify_ct(self, expected: &Tag<M>) -> Result<(), Error> {
// Check MAC using secure comparison
use subtle::ConstantTimeEq;

Expand All @@ -376,10 +387,11 @@ mod test_impl {
use super::*;
use aead::AeadMutInPlace;

impl<Cipher> NewAead for EaxImpl<Cipher>
impl<Cipher, M> NewAead for EaxImpl<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: ArrayLength<u8> + TagSize,
{
type KeySize = Cipher::KeySize;

Expand All @@ -395,21 +407,22 @@ mod test_impl {
}
}

impl<Cipher> AeadMutInPlace for super::EaxImpl<Cipher>
impl<Cipher, M> AeadMutInPlace for super::EaxImpl<Cipher, M>
where
Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
M: ArrayLength<u8> + TagSize,
{
type NonceSize = Cipher::BlockSize;
type TagSize = <Cmac<Cipher> as Mac>::OutputSize;
type TagSize = M;
type CiphertextOverhead = U0;

fn encrypt_in_place_detached(
&mut self,
nonce: &Nonce<Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
) -> Result<Tag, Error> {
) -> Result<Tag<M>, Error> {
// HACK: Reinitialize the instance
*self = Self::with_key_and_nonce(&self.key.clone(), nonce);

Expand All @@ -424,7 +437,7 @@ mod test_impl {
nonce: &Nonce<Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
expected_tag: &Tag,
expected_tag: &Tag<M>,
) -> Result<(), Error> {
// HACK: Reinitialize the instance
*self = Self::with_key_and_nonce(&self.key.clone(), nonce);
Expand Down
Loading

0 comments on commit ce1fdb2

Please sign in to comment.