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}