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}