aes-eme2-blake3 0.1.1

Maybe fast, deterministic AEAD construction using EME2 and BLAKE3 SIV
Documentation
#![no_std]
#![doc = include_str!("../README.md")]
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
)]
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)]

//! # Usage
//!
//! Simple usage (allocating, no associated data):
//!
#![cfg_attr(feature = "getrandom", doc = "```")]
#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
//! // NOTE: requires the `getrandom` feature is enabled
//!
//! use aes_eme2_blake3::{
//!     aead::{Aead, AeadCore, Generate, Key, KeyInit},
//!     Aes256Eme2Blake3, Nonce // Or `Aes128Eme2Blake3`
//! };
//!
//! let key = Key::<Aes256Eme2Blake3>::generate();
//! let cipher = Aes256Eme2Blake3::new(&key);
//!
//! let nonce = Nonce::generate(); // MUST be unique per message
//! let ciphertext = cipher.encrypt(&nonce, b"plaintext message".as_ref()).map_err(|_| "encrypt failed")?;
//!
//! let plaintext = cipher.decrypt(&nonce, ciphertext.as_ref()).map_err(|_| "decrypt failed")?;
//! assert_eq!(&plaintext, b"plaintext message");
//! # Ok(())
//! # }
//! ```
//!
//! ## In-place Usage
//!
//! The [`AeadInOut::encrypt_in_place`] and [`AeadInOut::decrypt_in_place`]
//! methods accept any type that impls the [`aead::Buffer`] trait which
//! contains the plaintext for encryption or ciphertext for decryption.
//!
//! Enabling the `arrayvec` feature of this crate will provide an impl of
//! [`aead::Buffer`] for `arrayvec::ArrayVec` (re-exported from the [`aead`] crate as
//! [`aead::arrayvec::ArrayVec`]), and enabling the `bytes` feature of this crate will
//! provide an impl of [`aead::Buffer`] for `bytes::BytesMut` (re-exported from the
//! [`aead`] crate as [`aead::bytes::BytesMut`]).
//!
//! It can then be passed as the `buffer` parameter to the in-place encrypt
//! and decrypt methods:
//!
#![cfg_attr(all(feature = "getrandom", feature = "arrayvec"), doc = "```")]
#![cfg_attr(
    not(all(feature = "getrandom", feature = "arrayvec")),
    doc = "```ignore"
)]
//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
//! // NOTE: requires the `arrayvec` and `getrandom` features are enabled
//!
//! use aes_eme2_blake3::{
//!     aead::{AeadCore, AeadInOut, Generate, Key, KeyInit, arrayvec::ArrayVec},
//!     Aes256Eme2Blake3, Nonce // Or `Aes128Eme2Blake3`
//! };
//!
//! let key = Key::<Aes256Eme2Blake3>::generate();
//! let cipher = Aes256Eme2Blake3::new(&key);
//!
//! let nonce = Nonce::generate(); // MUST be unique per message
//! let mut buffer: ArrayVec<u8, 128> = ArrayVec::new(); // Note: buffer needs 24-bytes overhead for auth tag
//! buffer.try_extend_from_slice(b"plaintext message").unwrap();
//!
//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
//! cipher.encrypt_in_place(&nonce, b"", &mut buffer).map_err(|_| "encrypt failed")?;
//!
//! // `buffer` now contains the message ciphertext
//! assert_ne!(buffer.as_ref(), b"plaintext message");
//!
//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
//! cipher.decrypt_in_place(&nonce, b"", &mut buffer).map_err(|_| "decrypt failed")?;
//! assert_eq!(buffer.as_ref(), b"plaintext message");
//! # Ok(())
//! # }
//! ```

extern crate alloc;

pub use aead::{self, AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser, TagPosition};
use aead::inout::InOutBuf;
use core::ops::Add;
use eme2::cipher::{
    array::Array,
    consts::{U16, U24, U32},
    BlockCipherDecrypt, BlockCipherEncrypt, BlockSizeUser,
};

#[cfg(feature = "aes")]
pub use aes;

use blake3::Hasher;
use eme2::cipher::InnerIvInit;
use eme2::Eme2;
use subtle::ConstantTimeEq;

#[cfg(feature = "zeroize")]
use zeroize::{Zeroize, ZeroizeOnDrop};

const SIV_CONTEXT: &str = "AES-EME2-BLAKE3-SIV-01";

/// External nonce for `Eme2Blake3`.
pub type Nonce<NonceSize = U16> = Array<u8, NonceSize>;

/// Tag for `Eme2Blake3`.
pub type Tag<TagSize = U24> = Array<u8, TagSize>;

/// EME2-BLAKE3 with AES-128.
#[cfg(feature = "aes")]
pub type Aes128Eme2Blake3 = Eme2Blake3<aes::Aes128>;

/// EME2-BLAKE3 with AES-256.
#[cfg(feature = "aes")]
pub type Aes256Eme2Blake3 = Eme2Blake3<aes::Aes256>;

/// A marker trait used to conditionally enforce `ZeroizeOnDrop` bounds when the `zeroize` feature is enabled.
#[cfg(feature = "zeroize")]
pub trait ZeroizeMarker: zeroize::ZeroizeOnDrop {}
#[cfg(feature = "zeroize")]
impl<T: zeroize::ZeroizeOnDrop> ZeroizeMarker for T {}

/// A marker trait used to conditionally enforce `ZeroizeOnDrop` bounds when the `zeroize` feature is enabled.
#[cfg(not(feature = "zeroize"))]
pub trait ZeroizeMarker {}
#[cfg(not(feature = "zeroize"))]
impl<T> ZeroizeMarker for T {}

/// A SIV (Synthetic Initialization Vector) AEAD cipher using EME2 and BLAKE3.
///
/// `Eme2Blake3` is cheap to clone and reuse across many messages.
#[derive(Clone)]
#[cfg_attr(feature = "zeroize", derive(ZeroizeOnDrop))]
pub struct Eme2Blake3<C>
where
    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
{
    cipher: C,
    base_hasher: Hasher,
}

impl<C> core::fmt::Debug for Eme2Blake3<C>
where
    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Eme2Blake3").finish_non_exhaustive()
    }
}

impl<C> KeySizeUser for Eme2Blake3<C>
where
    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + KeySizeUser + Clone + ZeroizeMarker,
    C::KeySize: Add<U32>,
    <C::KeySize as Add<U32>>::Output: eme2::cipher::array::ArraySize,
{
    type KeySize = <C::KeySize as Add<U32>>::Output;
}

impl<C> KeyInit for Eme2Blake3<C>
where
    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + KeySizeUser + KeyInit + Clone + ZeroizeMarker,
    C::KeySize: Add<U32>,
    <C::KeySize as Add<U32>>::Output: eme2::cipher::array::ArraySize,
{
    fn new(key: &Key<Self>) -> Self {
        use eme2::cipher::typenum::Unsigned;
        let enc_size = C::KeySize::USIZE;
        let (enc_slice, mac_slice) = key.split_at(enc_size);
        let cipher = C::new_from_slice(enc_slice).unwrap();
        let mut base_hasher = Hasher::new_keyed(mac_slice.try_into().unwrap());
        base_hasher.update(SIV_CONTEXT.as_bytes());

        Self { cipher, base_hasher }
    }
}

impl<C> AeadCore for Eme2Blake3<C>
where
    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
{
    type NonceSize = U16;
    type TagSize = U24;
    const TAG_POSITION: TagPosition = TagPosition::Prefix;
}

impl<C> AeadInOut for Eme2Blake3<C>
where
    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
{
    fn encrypt_inout_detached(
        &self,
        nonce: &aead::Nonce<Self>,
        associated_data: &[u8],
        mut buffer: InOutBuf<'_, '_, u8>,
    ) -> Result<aead::Tag<Self>, Error> {
        let plaintext_len = buffer.len();
        if plaintext_len < 16 {
            return Err(Error);
        }

        let in_data = buffer.get_in();
        let mut temp = alloc::vec::Vec::from(in_data);

        let tag_bytes = self.compute_siv(nonce, associated_data, &temp);
        
        let tweak: [u8; 16] = tag_bytes[..16].try_into().unwrap();
        let eme2_cipher = Eme2::inner_iv_init(self.cipher.clone(), &tweak.into());
        eme2_cipher.encrypt(&mut temp).map_err(|_| Error)?;

        buffer.get_out().copy_from_slice(&temp);

        #[cfg(feature = "zeroize")]
        temp.zeroize();

        Ok(tag_bytes.into())
    }

    fn decrypt_inout_detached(
        &self,
        nonce: &aead::Nonce<Self>,
        associated_data: &[u8],
        mut buffer: InOutBuf<'_, '_, u8>,
        tag: &aead::Tag<Self>,
    ) -> Result<(), Error> {
        if buffer.len() < 16 {
            return Err(Error);
        }

        let in_data = buffer.get_in();
        let mut temp = alloc::vec::Vec::from(in_data);

        let tweak: [u8; 16] = tag[..16].try_into().unwrap();
        let eme2_cipher = Eme2::inner_iv_init(self.cipher.clone(), &tweak.into());
        eme2_cipher.decrypt(&mut temp).map_err(|_| Error)?;

        let expected_tag = self.compute_siv(nonce, associated_data, &temp);

        if expected_tag.ct_eq(tag.as_slice()).unwrap_u8() == 0 {
            #[cfg(feature = "zeroize")]
            temp.zeroize();
            return Err(Error);
        }

        buffer.get_out().copy_from_slice(&temp);

        #[cfg(feature = "zeroize")]
        temp.zeroize();

        Ok(())
    }
}

impl<C> Eme2Blake3<C>
where
    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
{
    fn compute_siv(&self, nonce: &[u8], ad: &[u8], payload: &[u8]) -> [u8; 24] {
        let mut hasher = self.base_hasher.clone();
        hasher.update(nonce);
        hasher.update(&(ad.len() as u64).to_le_bytes());
        hasher.update(&(payload.len() as u64).to_le_bytes());
        hasher.update(ad);
        hasher.update(payload);

        let mut tag = [0u8; 24];
        hasher.finalize_xof().fill(&mut tag);
        tag
    }
}