Skip to main content

aes_siv/
siv.rs

1//! The Synthetic Initialization Vector (SIV) misuse-resistant block cipher
2//! mode of operation ([RFC 5297][1]). The interface is based on the [Rogaway paper][2].
3//!
4//! # Deterministic Authenticated Encryption Example
5//! Deterministic encryption with additional data. Suitable for example for key wrapping.
6//! Based on the test vector in [RFC 5297 Appendix: A1][3]
7#![cfg_attr(feature = "alloc", doc = "```")]
8#![cfg_attr(not(feature = "alloc"), doc = "```ignore")]
9//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
10//! use aes_siv::{siv::Aes128Siv, KeyInit};
11//! use hex_literal::hex;
12//!
13//! let key = hex!("fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
14//! let ad = hex!("101112131415161718191a1b1c1d1e1f2021222324252627");
15//! let plain_text = hex!("112233445566778899aabbccddee");
16//!
17//! let header = [&ad];
18//! let encrypt = Aes128Siv::new(&key.into())
19//!     .encrypt(&header, &plain_text)?;
20//!
21//! assert_eq!(
22//!     hex!("85632d07c6e8f37f950acd320a2ecc9340c02b9690c4dc04daef7f6afe5c").to_vec(),
23//!     encrypt
24//! );
25//! let decrypted = Aes128Siv::new(&key.into())
26//!        .decrypt(&header, &encrypt)?;
27//!
28//! assert_eq!(plain_text.to_vec(), decrypted);
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! # Nonce-Based Authenticated Encryption Example
34//! Nonce-based encryption with multiple additional data vectors.
35//! Based on the test vector in [RFC 5297 Appendix: A2][4]
36#![cfg_attr(feature = "alloc", doc = "```")]
37#![cfg_attr(not(feature = "alloc"), doc = "```ignore")]
38//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
39//! use aes_siv::{siv::Aes128Siv, KeyInit};
40//! use hex_literal::hex;
41//!
42//! let key = hex!("7f7e7d7c7b7a79787776757473727170404142434445464748494a4b4c4d4e4f");
43//! let ad1 = hex!("00112233445566778899aabbccddeeffdeaddadadeaddadaffeeddccbbaa99887766554433221100");
44//! let ad2 = hex!("102030405060708090a0");
45//! // Note that for production the nonce should be generated by for example: Aes256SivAead::generate_nonce
46//! let nonce = hex!("09f911029d74e35bd84156c5635688c0");
47//!
48//! let plain_text = hex!("7468697320697320736f6d6520706c61696e7465787420746f20656e6372797074207573696e67205349562d414553");
49//!
50//! let header: [&[u8]; 3] = [&ad1, &ad2, &nonce];
51//! let encrypt = Aes128Siv::new(&key.into())
52//!     .encrypt(&header, &plain_text)?;
53//!
54//! assert_eq!(
55//!     hex!("7bdb6e3b432667eb06f4d14bff2fbd0fcb900f2fddbe404326601965c889bf17dba77ceb094fa663b7a3f748ba8af829ea64ad544a272e9c485b62a3fd5c0d").to_vec(),
56//!     encrypt
57//! );
58//!
59//! let decrypted = Aes128Siv::new(&key.into())
60//!        .decrypt(&header, &encrypt)?;
61//!
62//! assert_eq!(plain_text.to_vec(), decrypted);
63//! # Ok(())
64//! # }
65//! ```
66//! [1]: https://tools.ietf.org/html/rfc5297
67//! [2]: https://web.cs.ucdavis.edu/~rogaway/papers/siv.pdf
68//! [3]: https://datatracker.ietf.org/doc/html/rfc5297#appendix-A.1
69//! [4]: https://datatracker.ietf.org/doc/html/rfc5297#appendix-A.2
70
71use crate::Tag;
72use aead::{
73    Buffer, Error,
74    array::{Array, ArraySize, typenum::U16},
75    inout::InOutBuf,
76};
77use aes::{Aes128, Aes256};
78use cipher::{
79    BlockCipherEncrypt, BlockSizeUser, InnerIvInit, Key, KeyInit, KeySizeUser, StreamCipherCore,
80};
81use cmac::Cmac;
82use core::{fmt, ops::Add};
83use dbl::Dbl;
84use digest::{CtOutput, FixedOutputReset, Mac};
85
86#[cfg(feature = "alloc")]
87use alloc::vec::Vec;
88#[cfg(feature = "pmac")]
89use pmac::Pmac;
90
91/// Size of the (synthetic) initialization vector in bytes
92pub const IV_SIZE: usize = 16;
93
94/// Maximum number of header items on the encrypted message
95pub const MAX_HEADERS: usize = 126;
96
97/// Counter mode with a 128-bit big endian counter.
98type Ctr128BE<C> = ctr::CtrCore<C, ctr::flavors::Ctr128BE>;
99
100/// Size of an AES-SIV key given a particular cipher
101pub(crate) type KeySize<C> = <<C as KeySizeUser>::KeySize as Add>::Output;
102
103/// Synthetic Initialization Vector (SIV) mode, providing misuse-resistant
104/// authenticated encryption (MRAE).
105pub struct Siv<C, M>
106where
107    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit + KeySizeUser,
108    M: Mac<OutputSize = U16>,
109{
110    encryption_key: Key<C>,
111    mac: M,
112}
113
114/// SIV modes based on CMAC
115pub type CmacSiv<BlockCipher> = Siv<BlockCipher, Cmac<BlockCipher>>;
116
117/// SIV modes based on PMAC
118#[cfg(feature = "pmac")]
119#[cfg_attr(docsrs, doc(cfg(feature = "pmac")))]
120pub type PmacSiv<BlockCipher> = Siv<BlockCipher, Pmac<BlockCipher>>;
121
122/// AES-CMAC-SIV with a 128-bit key
123pub type Aes128Siv = CmacSiv<Aes128>;
124
125/// AES-CMAC-SIV with a 256-bit key
126pub type Aes256Siv = CmacSiv<Aes256>;
127
128/// AES-PMAC-SIV with a 128-bit key
129#[cfg(feature = "pmac")]
130#[cfg_attr(docsrs, doc(cfg(feature = "pmac")))]
131pub type Aes128PmacSiv = PmacSiv<Aes128>;
132
133/// AES-PMAC-SIV with a 256-bit key
134#[cfg(feature = "pmac")]
135#[cfg_attr(docsrs, doc(cfg(feature = "pmac")))]
136pub type Aes256PmacSiv = PmacSiv<Aes256>;
137
138impl<C, M> KeySizeUser for Siv<C, M>
139where
140    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit + KeySizeUser,
141    M: Mac<OutputSize = U16> + FixedOutputReset + KeyInit,
142    <C as KeySizeUser>::KeySize: Add,
143    KeySize<C>: ArraySize,
144{
145    type KeySize = KeySize<C>;
146}
147
148impl<C, M> KeyInit for Siv<C, M>
149where
150    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit + KeySizeUser,
151    M: Mac<OutputSize = U16> + FixedOutputReset + KeyInit,
152    <C as KeySizeUser>::KeySize: Add,
153    KeySize<C>: ArraySize,
154{
155    /// Create a new AES-SIV instance
156    fn new(key: &Array<u8, KeySize<C>>) -> Self {
157        // Use the first half of the key as the MAC key and
158        // the second one as the encryption key
159        let (mac_key, enc_key) = key.split_at(M::key_size());
160
161        Self {
162            encryption_key: enc_key.try_into().expect("encryption key size mismatch"),
163            mac: <M as KeyInit>::new(mac_key.try_into().expect("MAC key size mismatch")),
164        }
165    }
166}
167
168impl<C, M> Siv<C, M>
169where
170    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit + KeySizeUser,
171    M: Mac<OutputSize = U16> + FixedOutputReset + KeyInit,
172{
173    /// Encrypt the given plaintext, allocating and returning a `Vec<u8>` for
174    /// the ciphertext.
175    ///
176    /// # Errors
177    ///
178    /// Returns [`Error`] if `plaintext.len()` is less than `M::OutputSize`.
179    /// Returns [`Error`] if `headers.len()` is greater than [`MAX_HEADERS`].
180    #[cfg(feature = "alloc")]
181    pub fn encrypt<I, T>(&mut self, headers: I, plaintext: &[u8]) -> Result<Vec<u8>, Error>
182    where
183        I: IntoIterator<Item = T>,
184        T: AsRef<[u8]>,
185    {
186        let mut buffer = Vec::with_capacity(plaintext.len() + IV_SIZE);
187        buffer.extend_from_slice(plaintext);
188        self.encrypt_in_place(headers, &mut buffer)?;
189        Ok(buffer)
190    }
191
192    /// Encrypt the given buffer containing a plaintext message in-place.
193    ///
194    /// # Errors
195    ///
196    /// Returns [`Error`] if `plaintext.len()` is less than `M::OutputSize`.
197    /// Returns [`Error`] if `headers.len()` is greater than [`MAX_HEADERS`].
198    pub fn encrypt_in_place<I, T>(
199        &mut self,
200        headers: I,
201        buffer: &mut dyn Buffer,
202    ) -> Result<(), Error>
203    where
204        I: IntoIterator<Item = T>,
205        T: AsRef<[u8]>,
206    {
207        let pt_len = buffer.len();
208
209        // Make room in the buffer for the SIV tag. It needs to be prepended.
210        buffer.extend_from_slice(Tag::default().as_slice())?;
211
212        // TODO(tarcieri): add offset param to `encrypt_inout_detached`
213        buffer.as_mut().copy_within(..pt_len, IV_SIZE);
214
215        let tag = self.encrypt_inout_detached(headers, (&mut buffer.as_mut()[IV_SIZE..]).into())?;
216        buffer.as_mut()[..IV_SIZE].copy_from_slice(tag.as_slice());
217        Ok(())
218    }
219
220    /// Encrypt the given plaintext in-place, returning the SIV tag on success.
221    ///
222    /// # Errors
223    ///
224    /// Returns [`Error`] if `plaintext.len()` is less than `M::OutputSize`.
225    /// Returns [`Error`] if `headers.len()` is greater than [`MAX_HEADERS`].
226    pub fn encrypt_inout_detached<I, T>(
227        &mut self,
228        headers: I,
229        plaintext: InOutBuf<'_, '_, u8>,
230    ) -> Result<Tag, Error>
231    where
232        I: IntoIterator<Item = T>,
233        T: AsRef<[u8]>,
234    {
235        // Compute the synthetic IV for this plaintext
236        let siv_tag = s2v(&mut self.mac, headers, plaintext.get_in())?;
237        self.xor_with_keystream(siv_tag, plaintext);
238        Ok(siv_tag)
239    }
240
241    /// Decrypt the given ciphertext, allocating and returning a `Vec<u8>` for the plaintext.
242    ///
243    /// # Errors
244    /// Returns [`Error`] if the provided authentication tag does not match the given ciphertext.
245    #[cfg(feature = "alloc")]
246    pub fn decrypt<I, T>(&mut self, headers: I, ciphertext: &[u8]) -> Result<Vec<u8>, Error>
247    where
248        I: IntoIterator<Item = T>,
249        T: AsRef<[u8]>,
250    {
251        let mut buffer = ciphertext.to_vec();
252        self.decrypt_in_place(headers, &mut buffer)?;
253        Ok(buffer)
254    }
255
256    /// Decrypt the message in-place, truncating the provided buffer to the length of the original
257    /// plaintext message upon success.
258    ///
259    /// # Errors
260    /// Returns [`Error`] if the provided authentication tag does not match the given ciphertext.
261    pub fn decrypt_in_place<I, T>(
262        &mut self,
263        headers: I,
264        buffer: &mut dyn Buffer,
265    ) -> Result<(), Error>
266    where
267        I: IntoIterator<Item = T>,
268        T: AsRef<[u8]>,
269    {
270        if buffer.len() < IV_SIZE {
271            return Err(Error);
272        }
273
274        let siv_tag = Tag::try_from(&buffer.as_ref()[..IV_SIZE]).map_err(|_| Error)?;
275        self.decrypt_inout_detached(headers, (&mut buffer.as_mut()[IV_SIZE..]).into(), &siv_tag)?;
276
277        let pt_len = buffer.len() - IV_SIZE;
278
279        // TODO(tarcieri): add offset param to `encrypt_inout_detached`
280        buffer.as_mut().copy_within(IV_SIZE.., 0);
281        buffer.truncate(pt_len);
282        Ok(())
283    }
284
285    /// Decrypt the given ciphertext in-place, authenticating it against the
286    /// provided SIV tag.
287    ///
288    /// # Errors
289    ///
290    /// Returns [`Error`] if the ciphertext is not authentic
291    pub fn decrypt_inout_detached<I, T>(
292        &mut self,
293        headers: I,
294        mut ciphertext: InOutBuf<'_, '_, u8>,
295        siv_tag: &Tag,
296    ) -> Result<(), Error>
297    where
298        I: IntoIterator<Item = T>,
299        T: AsRef<[u8]>,
300    {
301        self.xor_with_keystream(*siv_tag, ciphertext.reborrow());
302        let computed_siv_tag = s2v(&mut self.mac, headers, ciphertext.get_out())?;
303
304        // Note: `CtOutput` provides constant-time equality
305        if CtOutput::<M>::new(computed_siv_tag) == CtOutput::new(*siv_tag) {
306            Ok(())
307        } else {
308            // Re-encrypt the decrypted plaintext to avoid revealing it
309            self.xor_with_keystream(*siv_tag, ciphertext);
310            Err(Error)
311        }
312    }
313
314    /// XOR the given buffer with the keystream for the given IV
315    fn xor_with_keystream(&mut self, mut iv: Tag, msg: InOutBuf<'_, '_, u8>) {
316        // "We zero-out the top bit in each of the last two 32-bit words
317        // of the IV before assigning it to Ctr"
318        //  — http://web.cs.ucdavis.edu/~rogaway/papers/siv.pdf
319        iv[8] &= 0x7f;
320        iv[12] &= 0x7f;
321
322        Ctr128BE::<C>::inner_iv_init(C::new(&self.encryption_key), &iv)
323            .apply_keystream_partial(msg);
324    }
325}
326
327impl<C, M> fmt::Debug for Siv<C, M>
328where
329    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit + KeySizeUser,
330    M: Mac<OutputSize = U16>,
331{
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
333        f.debug_struct("Siv").finish_non_exhaustive()
334    }
335}
336
337impl<C, M> Drop for Siv<C, M>
338where
339    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit + KeySizeUser,
340    M: Mac<OutputSize = U16>,
341{
342    fn drop(&mut self) {
343        #[cfg(feature = "zeroize")]
344        {
345            use zeroize::Zeroize;
346            self.encryption_key.zeroize();
347        }
348    }
349}
350
351#[cfg(feature = "zeroize")]
352impl<C, M> zeroize::ZeroizeOnDrop for Siv<C, M>
353where
354    C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit + KeySizeUser,
355    M: Mac<OutputSize = U16>,
356{
357}
358
359/// "S2V" is a vectorized pseudorandom function (sometimes referred to as a
360/// vector MAC or "vMAC") which performs a "dbl"-and-xor operation on the
361/// outputs of a pseudo-random function (CMAC or PMAC).
362///
363/// In the RFC 5297 SIV construction (see Section 2.4), message headers
364/// (e.g. nonce, associated data) and the plaintext are used as inputs to
365/// S2V, together with a message authentication key. The output is the
366/// eponymous "synthetic IV" (SIV), which has a dual role as both
367/// initialization vector (for AES-CTR encryption) and MAC.
368fn s2v<M, I, T>(mac: &mut M, headers: I, message: &[u8]) -> Result<Tag, Error>
369where
370    M: Mac<OutputSize = U16> + FixedOutputReset,
371    I: IntoIterator<Item = T>,
372    T: AsRef<[u8]>,
373{
374    Mac::update(mac, &Tag::default());
375    let mut state = mac.finalize_reset().into_bytes();
376
377    for (i, header) in headers.into_iter().enumerate() {
378        if i >= MAX_HEADERS {
379            return Err(Error);
380        }
381
382        state = state.dbl();
383        Mac::update(mac, header.as_ref());
384        let code = mac.finalize_reset().into_bytes();
385        xor_in_place(&mut state, &code);
386    }
387
388    match message.len().checked_sub(IV_SIZE) {
389        Some(n) => {
390            Mac::update(mac, &message[..n]);
391            xor_in_place(&mut state, &message[n..]);
392        }
393        None => {
394            state = state.dbl();
395            xor_in_place(&mut state, message);
396            state[message.len()] ^= 0x80;
397        }
398    }
399
400    Mac::update(mac, state.as_ref());
401    Ok(mac.finalize_reset().into_bytes())
402}
403
404/// XOR the second argument into the first in-place. Slices do not have to be the same length.
405///
406/// # Panics
407/// If the destination slice is smaller than the source.
408#[inline]
409fn xor_in_place(dst: &mut [u8], src: &[u8]) {
410    for (a, b) in dst[..src.len()].iter_mut().zip(src) {
411        *a ^= *b;
412    }
413}