Skip to main content

aes_eme2_blake3/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
5    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
6)]
7#![forbid(unsafe_code)]
8#![cfg_attr(docsrs, feature(doc_cfg))]
9#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)]
10
11//! # Usage
12//!
13//! Simple usage (allocating, no associated data):
14//!
15#![cfg_attr(feature = "getrandom", doc = "```")]
16#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
17//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
18//! // NOTE: requires the `getrandom` feature is enabled
19//!
20//! use aes_eme2_blake3::{
21//!     aead::{Aead, AeadCore, Generate, Key, KeyInit},
22//!     Aes256Eme2Blake3, Nonce // Or `Aes128Eme2Blake3`
23//! };
24//!
25//! let key = Key::<Aes256Eme2Blake3>::generate();
26//! let cipher = Aes256Eme2Blake3::new(&key);
27//!
28//! let nonce = Nonce::generate(); // MUST be unique per message
29//! let ciphertext = cipher.encrypt(&nonce, b"plaintext message".as_ref()).map_err(|_| "encrypt failed")?;
30//!
31//! let plaintext = cipher.decrypt(&nonce, ciphertext.as_ref()).map_err(|_| "decrypt failed")?;
32//! assert_eq!(&plaintext, b"plaintext message");
33//! # Ok(())
34//! # }
35//! ```
36//!
37//! ## In-place Usage
38//!
39//! The [`AeadInOut::encrypt_in_place`] and [`AeadInOut::decrypt_in_place`]
40//! methods accept any type that impls the [`aead::Buffer`] trait which
41//! contains the plaintext for encryption or ciphertext for decryption.
42//!
43//! Enabling the `arrayvec` feature of this crate will provide an impl of
44//! [`aead::Buffer`] for `arrayvec::ArrayVec` (re-exported from the [`aead`] crate as
45//! [`aead::arrayvec::ArrayVec`]), and enabling the `bytes` feature of this crate will
46//! provide an impl of [`aead::Buffer`] for `bytes::BytesMut` (re-exported from the
47//! [`aead`] crate as [`aead::bytes::BytesMut`]).
48//!
49//! It can then be passed as the `buffer` parameter to the in-place encrypt
50//! and decrypt methods:
51//!
52#![cfg_attr(all(feature = "getrandom", feature = "arrayvec"), doc = "```")]
53#![cfg_attr(
54    not(all(feature = "getrandom", feature = "arrayvec")),
55    doc = "```ignore"
56)]
57//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
58//! // NOTE: requires the `arrayvec` and `getrandom` features are enabled
59//!
60//! use aes_eme2_blake3::{
61//!     aead::{AeadCore, AeadInOut, Generate, Key, KeyInit, arrayvec::ArrayVec},
62//!     Aes256Eme2Blake3, Nonce // Or `Aes128Eme2Blake3`
63//! };
64//!
65//! let key = Key::<Aes256Eme2Blake3>::generate();
66//! let cipher = Aes256Eme2Blake3::new(&key);
67//!
68//! let nonce = Nonce::generate(); // MUST be unique per message
69//! let mut buffer: ArrayVec<u8, 128> = ArrayVec::new(); // Note: buffer needs 24-bytes overhead for auth tag
70//! buffer.try_extend_from_slice(b"plaintext message").unwrap();
71//!
72//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
73//! cipher.encrypt_in_place(&nonce, b"", &mut buffer).map_err(|_| "encrypt failed")?;
74//!
75//! // `buffer` now contains the message ciphertext
76//! assert_ne!(buffer.as_ref(), b"plaintext message");
77//!
78//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
79//! cipher.decrypt_in_place(&nonce, b"", &mut buffer).map_err(|_| "decrypt failed")?;
80//! assert_eq!(buffer.as_ref(), b"plaintext message");
81//! # Ok(())
82//! # }
83//! ```
84
85extern crate alloc;
86
87pub use aead::{self, AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser, TagPosition};
88use aead::inout::InOutBuf;
89use core::ops::Add;
90use eme2::cipher::{
91    array::Array,
92    consts::{U16, U24, U32},
93    BlockCipherDecrypt, BlockCipherEncrypt, BlockSizeUser,
94};
95
96#[cfg(feature = "aes")]
97pub use aes;
98
99use blake3::Hasher;
100use eme2::cipher::InnerIvInit;
101use eme2::Eme2;
102use subtle::ConstantTimeEq;
103
104#[cfg(feature = "zeroize")]
105use zeroize::{Zeroize, ZeroizeOnDrop};
106
107const SIV_CONTEXT: &str = "AES-EME2-BLAKE3-SIV-01";
108
109/// External nonce for `Eme2Blake3`.
110pub type Nonce<NonceSize = U16> = Array<u8, NonceSize>;
111
112/// Tag for `Eme2Blake3`.
113pub type Tag<TagSize = U24> = Array<u8, TagSize>;
114
115/// EME2-BLAKE3 with AES-128.
116#[cfg(feature = "aes")]
117pub type Aes128Eme2Blake3 = Eme2Blake3<aes::Aes128>;
118
119/// EME2-BLAKE3 with AES-256.
120#[cfg(feature = "aes")]
121pub type Aes256Eme2Blake3 = Eme2Blake3<aes::Aes256>;
122
123/// A marker trait used to conditionally enforce `ZeroizeOnDrop` bounds when the `zeroize` feature is enabled.
124#[cfg(feature = "zeroize")]
125pub trait ZeroizeMarker: zeroize::ZeroizeOnDrop {}
126#[cfg(feature = "zeroize")]
127impl<T: zeroize::ZeroizeOnDrop> ZeroizeMarker for T {}
128
129/// A marker trait used to conditionally enforce `ZeroizeOnDrop` bounds when the `zeroize` feature is enabled.
130#[cfg(not(feature = "zeroize"))]
131pub trait ZeroizeMarker {}
132#[cfg(not(feature = "zeroize"))]
133impl<T> ZeroizeMarker for T {}
134
135/// A SIV (Synthetic Initialization Vector) AEAD cipher using EME2 and BLAKE3.
136///
137/// `Eme2Blake3` is cheap to clone and reuse across many messages.
138#[derive(Clone)]
139#[cfg_attr(feature = "zeroize", derive(ZeroizeOnDrop))]
140pub struct Eme2Blake3<C>
141where
142    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
143{
144    cipher: C,
145    base_hasher: Hasher,
146}
147
148impl<C> core::fmt::Debug for Eme2Blake3<C>
149where
150    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
151{
152    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
153        f.debug_struct("Eme2Blake3").finish_non_exhaustive()
154    }
155}
156
157impl<C> KeySizeUser for Eme2Blake3<C>
158where
159    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + KeySizeUser + Clone + ZeroizeMarker,
160    C::KeySize: Add<U32>,
161    <C::KeySize as Add<U32>>::Output: eme2::cipher::array::ArraySize,
162{
163    type KeySize = <C::KeySize as Add<U32>>::Output;
164}
165
166impl<C> KeyInit for Eme2Blake3<C>
167where
168    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + KeySizeUser + KeyInit + Clone + ZeroizeMarker,
169    C::KeySize: Add<U32>,
170    <C::KeySize as Add<U32>>::Output: eme2::cipher::array::ArraySize,
171{
172    fn new(key: &Key<Self>) -> Self {
173        use eme2::cipher::typenum::Unsigned;
174        let enc_size = C::KeySize::USIZE;
175        let (enc_slice, mac_slice) = key.split_at(enc_size);
176        let cipher = C::new_from_slice(enc_slice).unwrap();
177        let mut base_hasher = Hasher::new_keyed(mac_slice.try_into().unwrap());
178        base_hasher.update(SIV_CONTEXT.as_bytes());
179
180        Self { cipher, base_hasher }
181    }
182}
183
184impl<C> AeadCore for Eme2Blake3<C>
185where
186    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
187{
188    type NonceSize = U16;
189    type TagSize = U24;
190    const TAG_POSITION: TagPosition = TagPosition::Prefix;
191}
192
193impl<C> AeadInOut for Eme2Blake3<C>
194where
195    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
196{
197    fn encrypt_inout_detached(
198        &self,
199        nonce: &aead::Nonce<Self>,
200        associated_data: &[u8],
201        mut buffer: InOutBuf<'_, '_, u8>,
202    ) -> Result<aead::Tag<Self>, Error> {
203        let plaintext_len = buffer.len();
204        if plaintext_len < 16 {
205            return Err(Error);
206        }
207
208        let in_data = buffer.get_in();
209        let mut temp = alloc::vec::Vec::from(in_data);
210
211        let tag_bytes = self.compute_siv(nonce, associated_data, &temp);
212        
213        let tweak: [u8; 16] = tag_bytes[..16].try_into().unwrap();
214        let eme2_cipher = Eme2::inner_iv_init(self.cipher.clone(), &tweak.into());
215        eme2_cipher.encrypt(&mut temp).map_err(|_| Error)?;
216
217        buffer.get_out().copy_from_slice(&temp);
218
219        #[cfg(feature = "zeroize")]
220        temp.zeroize();
221
222        Ok(tag_bytes.into())
223    }
224
225    fn decrypt_inout_detached(
226        &self,
227        nonce: &aead::Nonce<Self>,
228        associated_data: &[u8],
229        mut buffer: InOutBuf<'_, '_, u8>,
230        tag: &aead::Tag<Self>,
231    ) -> Result<(), Error> {
232        if buffer.len() < 16 {
233            return Err(Error);
234        }
235
236        let in_data = buffer.get_in();
237        let mut temp = alloc::vec::Vec::from(in_data);
238
239        let tweak: [u8; 16] = tag[..16].try_into().unwrap();
240        let eme2_cipher = Eme2::inner_iv_init(self.cipher.clone(), &tweak.into());
241        eme2_cipher.decrypt(&mut temp).map_err(|_| Error)?;
242
243        let expected_tag = self.compute_siv(nonce, associated_data, &temp);
244
245        if expected_tag.ct_eq(tag.as_slice()).unwrap_u8() == 0 {
246            #[cfg(feature = "zeroize")]
247            temp.zeroize();
248            return Err(Error);
249        }
250
251        buffer.get_out().copy_from_slice(&temp);
252
253        #[cfg(feature = "zeroize")]
254        temp.zeroize();
255
256        Ok(())
257    }
258}
259
260impl<C> Eme2Blake3<C>
261where
262    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt + Clone + ZeroizeMarker,
263{
264    fn compute_siv(&self, nonce: &[u8], ad: &[u8], payload: &[u8]) -> [u8; 24] {
265        let mut hasher = self.base_hasher.clone();
266        hasher.update(nonce);
267        hasher.update(&(ad.len() as u64).to_le_bytes());
268        hasher.update(&(payload.len() as u64).to_le_bytes());
269        hasher.update(ad);
270        hasher.update(payload);
271
272        let mut tag = [0u8; 24];
273        hasher.finalize_xof().fill(&mut tag);
274        tag
275    }
276}