sb_aes_gcm_siv/
lib.rs

1//! [AES-GCM-SIV][1] ([RFC 8452][2]): high-performance
2//! [Authenticated Encryption with Associated Data (AEAD)][3] cipher which also
3//! provides [nonce reuse misuse resistance][4].
4//!
5//! Suitable as a general purpose symmetric encryption cipher, AES-GCM-SIV also
6//! removes many of the "sharp edges" of AES-GCM, providing significantly better
7//! security bounds while simultaneously eliminating the most catastrophic risks
8//! of nonce reuse that exist in AES-GCM.
9//!
10//! Decryption performance is equivalent to AES-GCM.
11//! Encryption is marginally slower.
12//!
13//! See also:
14//!
15//! - [Adam Langley: AES-GCM-SIV][5]
16//! - [Coda Hale: Towards A Safer Footgun][6]
17//!
18//! ## Performance Notes
19//!
20//! By default this crate will use software implementations of both AES and
21//! the POLYVAL universal hash function.
22//!
23//! When targeting modern x86/x86_64 CPUs, use the following `RUSTFLAGS` to
24//! take advantage of high performance AES-NI and CLMUL CPU intrinsics:
25//!
26//! ```text
27//! RUSTFLAGS="-Ctarget-cpu=sandybridge -Ctarget-feature=+aes,+sse2,+sse4.1,+ssse3"
28//! ```
29//!
30//! ## Security Warning
31//!
32//! No security audits of this crate have ever been performed.
33//!
34//! Some of this crate's dependencies were [audited by by NCC Group][7] as part of
35//! an audit of the `aes-gcm` crate, including the AES implementations (both AES-NI
36//! and a portable software implementation), as well as the `polyval` crate which
37//! is used as an authenticator. There were no significant findings.
38//!
39//! All implementations contained in the crate are designed to execute in constant
40//! time, either by relying on hardware intrinsics (i.e. AES-NI and CLMUL on
41//! x86/x86_64), or using a portable implementation which is only constant time
42//! on processors which implement constant-time multiplication.
43//!
44//! It is not suitable for use on processors with a variable-time multiplication
45//! operation (e.g. short circuit on multiply-by-zero / multiply-by-one, such as
46//! certain 32-bit PowerPC CPUs and some non-ARM microcontrollers).
47//!
48//! USE AT YOUR OWN RISK!
49//!
50//! # Usage
51//!
52//! Simple usage (allocating, no associated data):
53//!
54//! ```
55//! use aes_gcm_siv::{Aes256GcmSiv, Key, Nonce}; // Or `Aes128GcmSiv`
56//! use aes_gcm_siv::aead::{Aead, NewAead};
57//!
58//! let key = Key::from_slice(b"an example very very secret key.");
59//! let cipher = Aes256GcmSiv::new(key);
60//!
61//! let nonce = Nonce::from_slice(b"unique nonce"); // 96-bits; unique per message
62//!
63//! let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref())
64//!     .expect("encryption failure!");  // NOTE: handle this error to avoid panics!
65//!
66//!
67//! let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())
68//!     .expect("decryption failure!");  // NOTE: handle this error to avoid panics!
69//!
70//! assert_eq!(&plaintext, b"plaintext message");
71//! ```
72//!
73//! ## In-place Usage (eliminates `alloc` requirement)
74//!
75//! This crate has an optional `alloc` feature which can be disabled in e.g.
76//! microcontroller environments that don't have a heap.
77//!
78//! The [`AeadInPlace::encrypt_in_place`] and [`AeadInPlace::decrypt_in_place`]
79//! methods accept any type that impls the [`aead::Buffer`] trait which
80//! contains the plaintext for encryption or ciphertext for decryption.
81//!
82//! Note that if you enable the `heapless` feature of this crate,
83//! you will receive an impl of [`aead::Buffer`] for `heapless::Vec`
84//! (re-exported from the [`aead`] crate as [`aead::heapless::Vec`]),
85//! which can then be passed as the `buffer` parameter to the in-place encrypt
86//! and decrypt methods:
87//!
88//! ```
89//! # #[cfg(feature = "heapless")]
90//! # {
91//! use aes_gcm_siv::{Aes256GcmSiv, Key, Nonce}; // Or `Aes128GcmSiv`
92//! use aes_gcm_siv::aead::{AeadInPlace, NewAead};
93//! use aes_gcm_siv::aead::heapless::Vec;
94//!
95//! let key = Key::from_slice(b"an example very very secret key.");
96//! let cipher = Aes256GcmSiv::new(key);
97//!
98//! let nonce = Nonce::from_slice(b"unique nonce"); // 96-bits; unique per message
99//!
100//! let mut buffer: Vec<u8, 128> = Vec::new();
101//! buffer.extend_from_slice(b"plaintext message");
102//!
103//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
104//! cipher.encrypt_in_place(nonce, b"", &mut buffer).expect("encryption failure!");
105//!
106//! // `buffer` now contains the message ciphertext
107//! assert_ne!(&buffer, b"plaintext message");
108//!
109//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
110//! cipher.decrypt_in_place(nonce, b"", &mut buffer).expect("decryption failure!");
111//! assert_eq!(&buffer, b"plaintext message");
112//! # }
113//! ```
114//!
115//! [1]: https://en.wikipedia.org/wiki/AES-GCM-SIV
116//! [2]: https://tools.ietf.org/html/rfc8452
117//! [3]: https://en.wikipedia.org/wiki/Authenticated_encryption
118//! [4]: https://github.com/miscreant/meta/wiki/Nonce-Reuse-Misuse-Resistance
119//! [5]: https://www.imperialviolet.org/2017/05/14/aesgcmsiv.html
120//! [6]: https://codahale.com/towards-a-safer-footgun/
121//! [7]: https://research.nccgroup.com/2020/02/26/public-report-rustcrypto-aes-gcm-and-chacha20poly1305-implementation-review/
122
123#![no_std]
124#![doc(
125    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
126    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
127)]
128#![warn(missing_docs, rust_2018_idioms)]
129
130pub use aead;
131
132use aead::{AeadCore, AeadInPlace, Error, NewAead};
133use cipher::{
134    consts::{U0, U12, U16},
135    generic_array::{typenum::Unsigned, ArrayLength, GenericArray},
136    Block, BlockCipher, BlockEncrypt, FromBlockCipher, NewBlockCipher, StreamCipher,
137};
138use ctr::Ctr32LE;
139use polyval::{
140    universal_hash::{NewUniversalHash, UniversalHash},
141    Polyval,
142};
143use zeroize::Zeroize;
144
145/// AES is optional to allow swapping in hardware-specific backends
146#[cfg(feature = "aes")]
147use aes::{Aes128, Aes256};
148
149/// Maximum length of associated data (from RFC 8452 Section 6)
150pub const A_MAX: u64 = 1 << 36;
151
152/// Maximum length of plaintext (from RFC 8452 Section 6)
153pub const P_MAX: u64 = 1 << 36;
154
155/// Maximum length of ciphertext (from RFC 8452 Section 6)
156pub const C_MAX: u64 = (1 << 36) + 16;
157
158/// AES-GCM-SIV keys
159pub type Key<KeySize> = GenericArray<u8, KeySize>;
160
161/// AES-GCM-SIV nonces
162pub type Nonce = GenericArray<u8, U12>;
163
164/// AES-GCM-SIV tags
165pub type Tag = GenericArray<u8, U16>;
166
167/// AES-GCM-SIV with a 128-bit key
168#[cfg(feature = "aes")]
169pub type Aes128GcmSiv = AesGcmSiv<Aes128>;
170
171/// AES-GCM-SIV with a 256-bit key
172#[cfg(feature = "aes")]
173pub type Aes256GcmSiv = AesGcmSiv<Aes256>;
174
175/// AES-GCM-SIV: Misuse-Resistant Authenticated Encryption Cipher (RFC 8452)
176#[derive(Clone)]
177pub struct AesGcmSiv<Aes>
178where
179    Aes: BlockCipher<BlockSize = U16> + BlockEncrypt,
180    Aes::ParBlocks: ArrayLength<Block<Aes>>,
181{
182    /// Key generating key used to derive AES-GCM-SIV subkeys
183    key_generating_key: Aes,
184}
185
186impl<Aes> NewAead for AesGcmSiv<Aes>
187where
188    Aes: NewBlockCipher + BlockCipher<BlockSize = U16> + BlockEncrypt,
189    Aes::ParBlocks: ArrayLength<Block<Aes>>,
190{
191    type KeySize = Aes::KeySize;
192
193    fn new(key_bytes: &Key<Aes::KeySize>) -> Self {
194        Self {
195            key_generating_key: Aes::new(key_bytes),
196        }
197    }
198}
199
200impl<Aes> From<Aes> for AesGcmSiv<Aes>
201where
202    Aes: BlockCipher<BlockSize = U16> + BlockEncrypt,
203    Aes::ParBlocks: ArrayLength<Block<Aes>>,
204{
205    fn from(key_generating_key: Aes) -> Self {
206        Self { key_generating_key }
207    }
208}
209
210impl<Aes> AeadCore for AesGcmSiv<Aes>
211where
212    Aes: NewBlockCipher + BlockCipher<BlockSize = U16> + BlockEncrypt,
213    Aes::ParBlocks: ArrayLength<Block<Aes>>,
214{
215    type NonceSize = U12;
216    type TagSize = U16;
217    type CiphertextOverhead = U0;
218}
219
220impl<Aes> AeadInPlace for AesGcmSiv<Aes>
221where
222    Aes: NewBlockCipher + BlockCipher<BlockSize = U16> + BlockEncrypt,
223    Aes::ParBlocks: ArrayLength<Block<Aes>>,
224{
225    fn encrypt_in_place_detached(
226        &self,
227        nonce: &Nonce,
228        associated_data: &[u8],
229        buffer: &mut [u8],
230    ) -> Result<Tag, Error> {
231        Cipher::<Aes>::new(&self.key_generating_key, nonce)
232            .encrypt_in_place_detached(associated_data, buffer)
233    }
234
235    fn decrypt_in_place_detached(
236        &self,
237        nonce: &Nonce,
238        associated_data: &[u8],
239        buffer: &mut [u8],
240        tag: &Tag,
241    ) -> Result<(), Error> {
242        Cipher::<Aes>::new(&self.key_generating_key, nonce).decrypt_in_place_detached(
243            associated_data,
244            buffer,
245            tag,
246        )
247    }
248}
249
250/// AES-GCM-SIV: Misuse-Resistant Authenticated Encryption Cipher (RFC 8452)
251struct Cipher<Aes>
252where
253    Aes: BlockCipher<BlockSize = U16> + BlockEncrypt,
254    Aes::ParBlocks: ArrayLength<Block<Aes>>,
255{
256    /// Encryption cipher
257    enc_cipher: Aes,
258
259    /// POLYVAL universal hash
260    polyval: Polyval,
261
262    /// Nonce
263    nonce: Nonce,
264}
265
266impl<Aes> Cipher<Aes>
267where
268    Aes: NewBlockCipher + BlockCipher<BlockSize = U16> + BlockEncrypt,
269    Aes::ParBlocks: ArrayLength<Block<Aes>>,
270{
271    /// Initialize AES-GCM-SIV, deriving per-nonce message-authentication and
272    /// message-encryption keys.
273    pub(crate) fn new(key_generating_key: &Aes, nonce: &Nonce) -> Self {
274        let mut mac_key = polyval::Key::default();
275        let mut enc_key = Key::default();
276        let mut block = cipher::Block::<Aes>::default();
277        let mut counter = 0u32;
278
279        // Derive subkeys from the master key-generating-key in counter mode.
280        //
281        // From RFC 8452 Section 4:
282        // <https://tools.ietf.org/html/rfc8452#section-4>
283        //
284        // > The message-authentication key is 128 bit, and the message-encryption
285        // > key is either 128 (for AES-128) or 256 bit (for AES-256).
286        // >
287        // > These keys are generated by encrypting a series of plaintext blocks
288        // > that contain a 32-bit, little-endian counter followed by the nonce,
289        // > and then discarding the second half of the resulting ciphertext.  In
290        // > the AES-128 case, 128 + 128 = 256 bits of key material need to be
291        // > generated, and, since encrypting each block yields 64 bits after
292        // > discarding half, four blocks need to be encrypted.  The counter
293        // > values for these blocks are 0, 1, 2, and 3.  For AES-256, six blocks
294        // > are needed in total, with counter values 0 through 5 (inclusive).
295        for derived_key in &mut [mac_key.as_mut_slice(), enc_key.as_mut_slice()] {
296            for chunk in derived_key.chunks_mut(8) {
297                block[..4].copy_from_slice(&counter.to_le_bytes());
298                block[4..].copy_from_slice(nonce.as_slice());
299
300                key_generating_key.encrypt_block(&mut block);
301                chunk.copy_from_slice(&block.as_slice()[..8]);
302
303                counter += 1;
304            }
305        }
306
307        let result = Self {
308            enc_cipher: Aes::new(&enc_key),
309            polyval: Polyval::new(&mac_key),
310            nonce: *nonce,
311        };
312
313        // Zeroize all intermediate buffers
314        // TODO(tarcieri): use `Zeroizing` when const generics land
315        mac_key.as_mut_slice().zeroize();
316        enc_key.as_mut_slice().zeroize();
317        block.as_mut_slice().zeroize();
318
319        result
320    }
321
322    /// Encrypt the given message in-place, returning the authentication tag
323    pub(crate) fn encrypt_in_place_detached(
324        mut self,
325        associated_data: &[u8],
326        buffer: &mut [u8],
327    ) -> Result<Tag, Error> {
328        if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
329            return Err(Error);
330        }
331
332        let tag = self.compute_tag(associated_data, buffer);
333        init_ctr(&self.enc_cipher, &tag).apply_keystream(buffer);
334        Ok(tag)
335    }
336
337    /// Decrypt the given message, first authenticating ciphertext integrity
338    /// and returning an error if it's been tampered with.
339    pub(crate) fn decrypt_in_place_detached(
340        mut self,
341        associated_data: &[u8],
342        buffer: &mut [u8],
343        tag: &Tag,
344    ) -> Result<(), Error> {
345        if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX {
346            return Err(Error);
347        }
348
349        self.polyval.update_padded(associated_data);
350        let mut ctr = init_ctr(&self.enc_cipher, tag);
351
352        for chunk in buffer.chunks_mut(Aes::BlockSize::to_usize() * Aes::ParBlocks::to_usize()) {
353            ctr.apply_keystream(chunk);
354            self.polyval.update_padded(chunk);
355        }
356
357        let expected_tag = self.finish_tag(associated_data.len(), buffer.len());
358
359        use subtle::ConstantTimeEq;
360        if expected_tag.ct_eq(tag).unwrap_u8() == 1 {
361            Ok(())
362        } else {
363            // On MAC verify failure, re-encrypt the plaintext buffer to
364            // prevent accidental exposure.
365            init_ctr(&self.enc_cipher, tag).apply_keystream(buffer);
366            Err(Error)
367        }
368    }
369
370    /// Authenticate the given plaintext and associated data using POLYVAL
371    fn compute_tag(&mut self, associated_data: &[u8], buffer: &mut [u8]) -> Tag {
372        self.polyval.update_padded(associated_data);
373        self.polyval.update_padded(buffer);
374        self.finish_tag(associated_data.len(), buffer.len())
375    }
376
377    /// Finish computing POLYVAL tag for AAD and buffer of the given length
378    fn finish_tag(&mut self, associated_data_len: usize, buffer_len: usize) -> Tag {
379        let associated_data_bits = (associated_data_len as u64) * 8;
380        let buffer_bits = (buffer_len as u64) * 8;
381
382        let mut block = polyval::Block::default();
383        block[..8].copy_from_slice(&associated_data_bits.to_le_bytes());
384        block[8..].copy_from_slice(&buffer_bits.to_le_bytes());
385        self.polyval.update(&block);
386
387        let mut tag = self.polyval.finalize_reset().into_bytes();
388
389        // XOR the nonce into the resulting tag
390        for (i, byte) in tag[..12].iter_mut().enumerate() {
391            *byte ^= self.nonce[i];
392        }
393
394        // Clear the highest bit
395        tag[15] &= 0x7f;
396
397        self.enc_cipher.encrypt_block(&mut tag);
398        tag
399    }
400}
401
402/// Initialize counter mode.
403///
404/// From RFC 8452 Section 4:
405/// <https://tools.ietf.org/html/rfc8452#section-4>
406///
407/// > The initial counter block is the tag with the most significant bit
408/// > of the last byte set to one.
409fn init_ctr<Aes>(cipher: Aes, nonce: &cipher::Block<Aes>) -> Ctr32LE<Aes>
410where
411    Aes: BlockCipher<BlockSize = U16> + BlockEncrypt,
412    Aes::ParBlocks: ArrayLength<Block<Aes>>,
413{
414    let mut counter_block = *nonce;
415    counter_block[15] |= 0x80;
416    Ctr32LE::from_block_cipher(cipher, &counter_block)
417}