aes_siv/
siv.rs

1//! The Synthetic Initialization Vector (SIV) misuse-resistant block cipher
2//! mode of operation ([RFC 5297][1]).
3//!
4//! [1]: https://tools.ietf.org/html/rfc5297
5
6use crate::Tag;
7use aead::{
8    generic_array::{
9        typenum::{Unsigned, U16},
10        ArrayLength, GenericArray,
11    },
12    Buffer, Error,
13};
14use aes::{Aes128, Aes256};
15use cipher::{
16    BlockCipher, BlockEncryptMut, InnerIvInit, Key, KeyInit, KeySizeUser, StreamCipherCore,
17};
18use cmac::Cmac;
19use core::ops::Add;
20use dbl::Dbl;
21use digest::{CtOutput, FixedOutputReset, Mac};
22use zeroize::Zeroize;
23
24#[cfg(feature = "alloc")]
25use alloc::vec::Vec;
26
27#[cfg(feature = "pmac")]
28use pmac::Pmac;
29
30/// Size of the (synthetic) initialization vector in bytes
31pub const IV_SIZE: usize = 16;
32
33/// Maximum number of header items on the encrypted message
34pub const MAX_HEADERS: usize = 126;
35
36/// Counter mode with a 128-bit big endian counter.
37type Ctr128BE<C> = ctr::CtrCore<C, ctr::flavors::Ctr128BE>;
38
39/// Size of an AES-SIV key given a particular cipher
40pub type KeySize<C> = <<C as KeySizeUser>::KeySize as Add>::Output;
41
42/// Synthetic Initialization Vector (SIV) mode, providing misuse-resistant
43/// authenticated encryption (MRAE).
44pub struct Siv<C, M>
45where
46    C: BlockCipher<BlockSize = U16> + BlockEncryptMut + KeyInit + KeySizeUser,
47    M: Mac<OutputSize = U16>,
48{
49    encryption_key: Key<C>,
50    mac: M,
51}
52
53/// SIV modes based on CMAC
54pub type CmacSiv<BlockCipher> = Siv<BlockCipher, Cmac<BlockCipher>>;
55
56/// SIV modes based on PMAC
57#[cfg(feature = "pmac")]
58#[cfg_attr(docsrs, doc(cfg(feature = "pmac")))]
59pub type PmacSiv<BlockCipher> = Siv<BlockCipher, Pmac<BlockCipher>>;
60
61/// AES-CMAC-SIV with a 128-bit key
62pub type Aes128Siv = CmacSiv<Aes128>;
63
64/// AES-CMAC-SIV with a 256-bit key
65pub type Aes256Siv = CmacSiv<Aes256>;
66
67/// AES-PMAC-SIV with a 128-bit key
68#[cfg(feature = "pmac")]
69#[cfg_attr(docsrs, doc(cfg(feature = "pmac")))]
70pub type Aes128PmacSiv = PmacSiv<Aes128>;
71
72/// AES-PMAC-SIV with a 256-bit key
73#[cfg(feature = "pmac")]
74#[cfg_attr(docsrs, doc(cfg(feature = "pmac")))]
75pub type Aes256PmacSiv = PmacSiv<Aes256>;
76
77impl<C, M> KeySizeUser for Siv<C, M>
78where
79    C: BlockCipher<BlockSize = U16> + BlockEncryptMut + KeyInit + KeySizeUser,
80    M: Mac<OutputSize = U16> + FixedOutputReset + KeyInit,
81    <C as KeySizeUser>::KeySize: Add,
82    KeySize<C>: ArrayLength<u8>,
83{
84    type KeySize = KeySize<C>;
85}
86
87impl<C, M> KeyInit for Siv<C, M>
88where
89    C: BlockCipher<BlockSize = U16> + BlockEncryptMut + KeyInit + KeySizeUser,
90    M: Mac<OutputSize = U16> + FixedOutputReset + KeyInit,
91    <C as KeySizeUser>::KeySize: Add,
92    KeySize<C>: ArrayLength<u8>,
93{
94    /// Create a new AES-SIV instance
95    fn new(key: &GenericArray<u8, KeySize<C>>) -> Self {
96        // Use the first half of the key as the encryption key
97        let encryption_key = GenericArray::clone_from_slice(&key[M::key_size()..]);
98
99        // Use the second half of the key as the MAC key
100        let mac = <M as Mac>::new(GenericArray::from_slice(&key[..M::KeySize::to_usize()]));
101
102        Self {
103            encryption_key,
104            mac,
105        }
106    }
107}
108
109impl<C, M> Siv<C, M>
110where
111    C: BlockCipher<BlockSize = U16> + BlockEncryptMut + KeyInit + KeySizeUser,
112    M: Mac<OutputSize = U16> + FixedOutputReset + KeyInit,
113{
114    /// Encrypt the given plaintext, allocating and returning a `Vec<u8>` for
115    /// the ciphertext.
116    ///
117    /// # Errors
118    ///
119    /// Returns [`Error`] if `plaintext.len()` is less than `M::OutputSize`.
120    /// Returns [`Error`] if `headers.len()` is greater than [`MAX_HEADERS`].
121    #[cfg(feature = "alloc")]
122    pub fn encrypt<I, T>(&mut self, headers: I, plaintext: &[u8]) -> Result<Vec<u8>, Error>
123    where
124        I: IntoIterator<Item = T>,
125        T: AsRef<[u8]>,
126    {
127        let mut buffer = Vec::with_capacity(plaintext.len() + IV_SIZE);
128        buffer.extend_from_slice(plaintext);
129        self.encrypt_in_place(headers, &mut buffer)?;
130        Ok(buffer)
131    }
132
133    /// Encrypt the given buffer containing a plaintext message in-place.
134    ///
135    /// # Errors
136    ///
137    /// Returns [`Error`] if `plaintext.len()` is less than `M::OutputSize`.
138    /// Returns [`Error`] if `headers.len()` is greater than [`MAX_HEADERS`].
139    pub fn encrypt_in_place<I, T>(
140        &mut self,
141        headers: I,
142        buffer: &mut dyn Buffer,
143    ) -> Result<(), Error>
144    where
145        I: IntoIterator<Item = T>,
146        T: AsRef<[u8]>,
147    {
148        let pt_len = buffer.len();
149
150        // Make room in the buffer for the SIV tag. It needs to be prepended.
151        buffer.extend_from_slice(Tag::default().as_slice())?;
152
153        // TODO(tarcieri): add offset param to `encrypt_in_place_detached`
154        buffer.as_mut().copy_within(..pt_len, IV_SIZE);
155
156        let tag = self.encrypt_in_place_detached(headers, &mut buffer.as_mut()[IV_SIZE..])?;
157        buffer.as_mut()[..IV_SIZE].copy_from_slice(tag.as_slice());
158        Ok(())
159    }
160
161    /// Encrypt the given plaintext in-place, returning the SIV tag on success.
162    ///
163    /// # Errors
164    ///
165    /// Returns [`Error`] if `plaintext.len()` is less than `M::OutputSize`.
166    /// Returns [`Error`] if `headers.len()` is greater than [`MAX_HEADERS`].
167    pub fn encrypt_in_place_detached<I, T>(
168        &mut self,
169        headers: I,
170        plaintext: &mut [u8],
171    ) -> Result<Tag, Error>
172    where
173        I: IntoIterator<Item = T>,
174        T: AsRef<[u8]>,
175    {
176        // Compute the synthetic IV for this plaintext
177        let siv_tag = s2v(&mut self.mac, headers, plaintext)?;
178        self.xor_with_keystream(siv_tag, plaintext);
179        Ok(siv_tag)
180    }
181
182    /// Decrypt the given ciphertext, allocating and returning a Vec<u8> for the plaintext
183    #[cfg(feature = "alloc")]
184    pub fn decrypt<I, T>(&mut self, headers: I, ciphertext: &[u8]) -> Result<Vec<u8>, Error>
185    where
186        I: IntoIterator<Item = T>,
187        T: AsRef<[u8]>,
188    {
189        let mut buffer = ciphertext.to_vec();
190        self.decrypt_in_place(headers, &mut buffer)?;
191        Ok(buffer)
192    }
193
194    /// Decrypt the message in-place, returning an error in the event the
195    /// provided authentication tag does not match the given ciphertext.
196    ///
197    /// The buffer will be truncated to the length of the original plaintext
198    /// message upon success.
199    pub fn decrypt_in_place<I, T>(
200        &mut self,
201        headers: I,
202        buffer: &mut dyn Buffer,
203    ) -> Result<(), Error>
204    where
205        I: IntoIterator<Item = T>,
206        T: AsRef<[u8]>,
207    {
208        if buffer.len() < IV_SIZE {
209            return Err(Error);
210        }
211
212        let siv_tag = Tag::clone_from_slice(&buffer.as_ref()[..IV_SIZE]);
213        self.decrypt_in_place_detached(headers, &mut buffer.as_mut()[IV_SIZE..], &siv_tag)?;
214
215        let pt_len = buffer.len() - IV_SIZE;
216
217        // TODO(tarcieri): add offset param to `encrypt_in_place_detached`
218        buffer.as_mut().copy_within(IV_SIZE.., 0);
219        buffer.truncate(pt_len);
220        Ok(())
221    }
222
223    /// Decrypt the given ciphertext in-place, authenticating it against the
224    /// provided SIV tag.
225    ///
226    /// # Errors
227    ///
228    /// Returns [`Error`] if the ciphertext is not authentic
229    pub fn decrypt_in_place_detached<I, T>(
230        &mut self,
231        headers: I,
232        ciphertext: &mut [u8],
233        siv_tag: &Tag,
234    ) -> Result<(), Error>
235    where
236        I: IntoIterator<Item = T>,
237        T: AsRef<[u8]>,
238    {
239        self.xor_with_keystream(*siv_tag, ciphertext);
240        let computed_siv_tag = s2v(&mut self.mac, headers, ciphertext)?;
241
242        // Note: `CtOutput` provides constant-time equality
243        if CtOutput::<M>::new(computed_siv_tag) == CtOutput::new(*siv_tag) {
244            Ok(())
245        } else {
246            // Re-encrypt the decrypted plaintext to avoid revealing it
247            self.xor_with_keystream(*siv_tag, ciphertext);
248            Err(Error)
249        }
250    }
251
252    /// XOR the given buffer with the keystream for the given IV
253    fn xor_with_keystream(&mut self, mut iv: Tag, msg: &mut [u8]) {
254        // "We zero-out the top bit in each of the last two 32-bit words
255        // of the IV before assigning it to Ctr"
256        //  — http://web.cs.ucdavis.edu/~rogaway/papers/siv.pdf
257        iv[8] &= 0x7f;
258        iv[12] &= 0x7f;
259
260        Ctr128BE::<C>::inner_iv_init(C::new(&self.encryption_key), &iv)
261            .apply_keystream_partial(msg.into());
262    }
263}
264
265impl<C, M> Drop for Siv<C, M>
266where
267    C: BlockCipher<BlockSize = U16> + BlockEncryptMut + KeyInit + KeySizeUser,
268    M: Mac<OutputSize = U16>,
269{
270    fn drop(&mut self) {
271        self.encryption_key.zeroize()
272    }
273}
274
275/// "S2V" is a vectorized pseudorandom function (sometimes referred to as a
276/// vector MAC or "vMAC") which performs a "dbl"-and-xor operation on the
277/// outputs of a pseudo-random function (CMAC or PMAC).
278///
279/// In the RFC 5297 SIV construction (see Section 2.4), message headers
280/// (e.g. nonce, associated data) and the plaintext are used as inputs to
281/// S2V, together with a message authentication key. The output is the
282/// eponymous "synthetic IV" (SIV), which has a dual role as both
283/// initialization vector (for AES-CTR encryption) and MAC.
284fn s2v<M, I, T>(mac: &mut M, headers: I, message: &[u8]) -> Result<Tag, Error>
285where
286    M: Mac<OutputSize = U16> + FixedOutputReset,
287    I: IntoIterator<Item = T>,
288    T: AsRef<[u8]>,
289{
290    Mac::update(mac, &Tag::default());
291    let mut state = mac.finalize_reset().into_bytes();
292
293    for (i, header) in headers.into_iter().enumerate() {
294        if i >= MAX_HEADERS {
295            return Err(Error);
296        }
297
298        state = state.dbl();
299        Mac::update(mac, header.as_ref());
300        let code = mac.finalize_reset().into_bytes();
301        xor_in_place(&mut state, &code);
302    }
303
304    if message.len() >= IV_SIZE {
305        let n = message.len().checked_sub(IV_SIZE).unwrap();
306
307        Mac::update(mac, &message[..n]);
308        xor_in_place(&mut state, &message[n..]);
309    } else {
310        state = state.dbl();
311        xor_in_place(&mut state, message);
312        state[message.len()] ^= 0x80;
313    };
314
315    Mac::update(mac, state.as_ref());
316    Ok(mac.finalize_reset().into_bytes())
317}
318
319/// XOR the second argument into the first in-place. Slices do not have to be
320/// aligned in memory.
321///
322/// Panics if the destination slice is smaller than the source.
323#[inline]
324fn xor_in_place(dst: &mut [u8], src: &[u8]) {
325    for (a, b) in dst[..src.len()].iter_mut().zip(src) {
326        *a ^= *b;
327    }
328}