Skip to main content

aes_gcm_siv/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
6    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
7)]
8#![warn(missing_docs, rust_2018_idioms)]
9
10//! # Usage
11//!
12//! Simple usage (allocating, no associated data):
13//!
14#![cfg_attr(feature = "getrandom", doc = "```")]
15#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
16//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
17//! // NOTE: requires the `getrandom` feature is enabled
18//!
19//! use aes_gcm_siv::{
20//!     aead::{Aead, AeadCore, Generate, Key, KeyInit},
21//!     Aes256GcmSiv, Nonce // Or `Aes128GcmSiv`
22//! };
23//!
24//! let key = Key::<Aes256GcmSiv>::generate();
25//! let cipher = Aes256GcmSiv::new(&key);
26//!
27//! let nonce = Nonce::generate(); // MUST be unique per message
28//! let ciphertext = cipher.encrypt(&nonce, b"plaintext message".as_ref())?;
29//!
30//! let plaintext = cipher.decrypt(&nonce, ciphertext.as_ref())?;
31//! assert_eq!(&plaintext, b"plaintext message");
32//! # Ok(())
33//! # }
34//! ```
35//!
36//! ## In-place Usage (eliminates `alloc` requirement)
37//!
38//! This crate has an optional `alloc` feature which can be disabled in e.g.
39//! microcontroller environments that don't have a heap.
40//!
41//! The [`AeadInOut::encrypt_in_place`] and [`AeadInOut::decrypt_in_place`]
42//! methods accept any type that impls the [`aead::Buffer`] trait which
43//! contains the plaintext for encryption or ciphertext for decryption.
44//!
45//! Enabling the `arrayvec` feature of this crate will provide an impl of
46//! [`aead::Buffer`] for `arrayvec::ArrayVec` (re-exported from the [`aead`] crate as
47//! [`aead::arrayvec::ArrayVec`]), and enabling the `bytes` feature of this crate will
48//! provide an impl of [`aead::Buffer`] for `bytes::BytesMut` (re-exported from the
49//! [`aead`] crate as [`aead::bytes::BytesMut`]).
50//!
51//! It can then be passed as the `buffer` parameter to the in-place encrypt
52//! and decrypt methods:
53//!
54#![cfg_attr(all(feature = "getrandom", feature = "arrayvec"), doc = "```")]
55#![cfg_attr(
56    not(all(feature = "getrandom", feature = "arrayvec")),
57    doc = "```ignore"
58)]
59//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
60//! // NOTE: requires the `arrayvec` and `getrandom` features are enabled
61//!
62//! use aes_gcm_siv::{
63//!     aead::{AeadInOut, AeadCore, Buffer, Generate, Key, KeyInit, arrayvec::ArrayVec},
64//!     Aes256GcmSiv, Nonce, // Or `Aes128GcmSiv`
65//! };
66//!
67//! let key = Key::<Aes256GcmSiv>::generate();
68//! let cipher = Aes256GcmSiv::new(&key);
69//!
70//! let nonce = Nonce::generate(); // 96-bits; unique per message
71//! let mut buffer: ArrayVec<u8, 128> = ArrayVec::new(); // Note: buffer needs 16-bytes overhead for auth tag
72//! buffer.extend_from_slice(b"plaintext message");
73//!
74//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
75//! cipher.encrypt_in_place(&nonce, b"", &mut buffer)?;
76//!
77//! // `buffer` now contains the message ciphertext
78//! assert_ne!(buffer.as_ref(), b"plaintext message");
79//!
80//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
81//! cipher.decrypt_in_place(&nonce, b"", &mut buffer)?;
82//! assert_eq!(buffer.as_ref(), b"plaintext message");
83//! # Ok(())
84//! # }
85//! ```
86
87pub use aead::{self, AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser};
88
89#[cfg(feature = "aes")]
90pub use aes;
91
92use aead::{TagPosition, inout::InOutBuf};
93use cipher::{
94    BlockCipherEncrypt, BlockSizeUser, InnerIvInit, StreamCipherCore,
95    array::Array,
96    consts::{U12, U16},
97};
98use polyval::{Polyval, universal_hash::UniversalHash};
99
100/// AES is optional to allow swapping in hardware-specific backends.
101#[cfg(feature = "aes")]
102use aes::{Aes128, Aes256};
103
104/// Maximum length of associated data (from RFC8452 § 6).
105pub const A_MAX: u64 = 1 << 36;
106
107/// Maximum length of plaintext (from RFC8452 § 6).
108pub const P_MAX: u64 = 1 << 36;
109
110/// Maximum length of ciphertext (from RFC8452 § 6).
111pub const C_MAX: u64 = (1 << 36) + 16;
112
113/// AES-GCM-SIV nonces.
114pub type Nonce = Array<u8, U12>;
115
116/// AES-GCM-SIV tags.
117pub type Tag = Array<u8, U16>;
118
119/// AES-GCM-SIV with a 128-bit key.
120#[cfg(feature = "aes")]
121pub type Aes128GcmSiv = AesGcmSiv<Aes128>;
122
123/// AES-GCM-SIV with a 256-bit key.
124#[cfg(feature = "aes")]
125pub type Aes256GcmSiv = AesGcmSiv<Aes256>;
126
127/// Counter mode with a 32-bit little endian counter.
128type Ctr32LE<Aes> = ctr::CtrCore<Aes, ctr::flavors::Ctr32LE>;
129
130/// AES-GCM-SIV: Misuse-Resistant Authenticated Encryption Cipher (RFC 8452).
131#[derive(Clone)]
132pub struct AesGcmSiv<Aes> {
133    /// Key generating key used to derive AES-GCM-SIV subkeys.
134    key_generating_key: Aes,
135}
136
137impl<Aes> KeySizeUser for AesGcmSiv<Aes>
138where
139    Aes: KeySizeUser,
140{
141    type KeySize = Aes::KeySize;
142}
143
144impl<Aes> KeyInit for AesGcmSiv<Aes>
145where
146    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit,
147{
148    fn new(key_bytes: &Key<Self>) -> Self {
149        Self {
150            key_generating_key: Aes::new(key_bytes),
151        }
152    }
153}
154
155impl<Aes> From<Aes> for AesGcmSiv<Aes>
156where
157    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
158{
159    fn from(key_generating_key: Aes) -> Self {
160        Self { key_generating_key }
161    }
162}
163
164impl<Aes> AeadCore for AesGcmSiv<Aes>
165where
166    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit,
167{
168    type NonceSize = U12;
169    type TagSize = U16;
170    const TAG_POSITION: TagPosition = TagPosition::Postfix;
171}
172
173impl<Aes> AeadInOut for AesGcmSiv<Aes>
174where
175    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit,
176{
177    fn encrypt_inout_detached(
178        &self,
179        nonce: &Nonce,
180        associated_data: &[u8],
181        buffer: InOutBuf<'_, '_, u8>,
182    ) -> Result<Tag, Error> {
183        Cipher::<Aes>::new(&self.key_generating_key, nonce)
184            .encrypt_inout_detached(associated_data, buffer)
185    }
186
187    fn decrypt_inout_detached(
188        &self,
189        nonce: &Nonce,
190        associated_data: &[u8],
191        buffer: InOutBuf<'_, '_, u8>,
192        tag: &Tag,
193    ) -> Result<(), Error> {
194        Cipher::<Aes>::new(&self.key_generating_key, nonce).decrypt_inout_detached(
195            associated_data,
196            buffer,
197            tag,
198        )
199    }
200}
201
202/// AES-GCM-SIV: Misuse-Resistant Authenticated Encryption Cipher (RFC8452).
203struct Cipher<Aes>
204where
205    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
206{
207    /// Encryption cipher.
208    enc_cipher: Aes,
209
210    /// POLYVAL universal hash.
211    polyval: Polyval,
212
213    /// Nonce.
214    nonce: Nonce,
215}
216
217impl<Aes> Cipher<Aes>
218where
219    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit,
220{
221    /// Initialize AES-GCM-SIV, deriving per-nonce message-authentication and
222    /// message-encryption keys.
223    pub(crate) fn new(key_generating_key: &Aes, nonce: &Nonce) -> Self {
224        let mut mac_key = polyval::Key::default();
225        let mut enc_key = Array::default();
226        let mut block = cipher::Block::<Aes>::default();
227        let mut counter = 0u32;
228
229        // Derive subkeys from the master key-generating-key in counter mode.
230        //
231        // From RFC8452 § 4: <https://tools.ietf.org/html/rfc8452#section-4>
232        //
233        // > The message-authentication key is 128 bit, and the message-encryption
234        // > key is either 128 (for AES-128) or 256 bit (for AES-256).
235        // >
236        // > These keys are generated by encrypting a series of plaintext blocks
237        // > that contain a 32-bit, little-endian counter followed by the nonce,
238        // > and then discarding the second half of the resulting ciphertext.  In
239        // > the AES-128 case, 128 + 128 = 256 bits of key material need to be
240        // > generated, and, since encrypting each block yields 64 bits after
241        // > discarding half, four blocks need to be encrypted.  The counter
242        // > values for these blocks are 0, 1, 2, and 3.  For AES-256, six blocks
243        // > are needed in total, with counter values 0 through 5 (inclusive).
244        for derived_key in &mut [mac_key.as_mut_slice(), enc_key.as_mut_slice()] {
245            for chunk in derived_key.chunks_mut(8) {
246                block[..4].copy_from_slice(&counter.to_le_bytes());
247                block[4..].copy_from_slice(nonce.as_slice());
248
249                key_generating_key.encrypt_block(&mut block);
250                chunk.copy_from_slice(&block.as_slice()[..8]);
251
252                counter += 1;
253            }
254        }
255
256        let result = Self {
257            enc_cipher: Aes::new(&enc_key),
258            polyval: Polyval::new(&mac_key),
259            nonce: *nonce,
260        };
261
262        // Zeroize all intermediate buffers
263        // TODO(tarcieri): use `Zeroizing` when const generics land
264        #[cfg(feature = "zeroize")]
265        {
266            use zeroize::Zeroize;
267            mac_key.as_mut_slice().zeroize();
268            enc_key.as_mut_slice().zeroize();
269            block.as_mut_slice().zeroize();
270        }
271
272        result
273    }
274
275    /// Encrypt the given message in-place, returning the authentication tag.
276    pub(crate) fn encrypt_inout_detached(
277        mut self,
278        associated_data: &[u8],
279        buffer: InOutBuf<'_, '_, u8>,
280    ) -> Result<Tag, Error> {
281        if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
282            return Err(Error);
283        }
284
285        self.polyval.update_padded(associated_data);
286        self.polyval.update_padded(buffer.get_in());
287
288        let tag = self.finish_tag(associated_data.len(), buffer.len());
289        init_ctr(&self.enc_cipher, &tag).apply_keystream_partial(buffer);
290
291        Ok(tag)
292    }
293
294    /// Decrypt the given message, first authenticating ciphertext integrity
295    /// and returning an error if it's been tampered with.
296    pub(crate) fn decrypt_inout_detached(
297        mut self,
298        associated_data: &[u8],
299        mut buffer: InOutBuf<'_, '_, u8>,
300        tag: &Tag,
301    ) -> Result<(), Error> {
302        if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX {
303            return Err(Error);
304        }
305
306        self.polyval.update_padded(associated_data);
307
308        // TODO(tarcieri): interleave decryption and authentication
309        init_ctr(&self.enc_cipher, tag).apply_keystream_partial(buffer.reborrow());
310        self.polyval.update_padded(buffer.get_out());
311
312        let expected_tag = self.finish_tag(associated_data.len(), buffer.len());
313
314        use subtle::ConstantTimeEq;
315        if expected_tag.ct_eq(tag).into() {
316            Ok(())
317        } else {
318            // On MAC verify failure, re-encrypt the plaintext buffer to
319            // prevent accidental exposure.
320            init_ctr(&self.enc_cipher, tag).apply_keystream_partial(buffer);
321            Err(Error)
322        }
323    }
324
325    /// Finish computing POLYVAL tag for AAD and buffer of the given length.
326    fn finish_tag(&mut self, associated_data_len: usize, buffer_len: usize) -> Tag {
327        let associated_data_bits = (associated_data_len as u64) * 8;
328        let buffer_bits = (buffer_len as u64) * 8;
329
330        let mut block = polyval::Block::default();
331        block[..8].copy_from_slice(&associated_data_bits.to_le_bytes());
332        block[8..].copy_from_slice(&buffer_bits.to_le_bytes());
333        self.polyval.update(&[block]);
334
335        let mut tag = self.polyval.finalize_reset();
336
337        // XOR the nonce into the resulting tag
338        for (i, byte) in tag[..12].iter_mut().enumerate() {
339            *byte ^= self.nonce[i];
340        }
341
342        // Clear the highest bit
343        tag[15] &= 0x7f;
344
345        self.enc_cipher.encrypt_block(&mut tag);
346        tag
347    }
348}
349
350/// Initialize counter mode.
351///
352/// From RFC8452 § 4: <https://tools.ietf.org/html/rfc8452#section-4>
353///
354/// > The initial counter block is the tag with the most significant bit
355/// > of the last byte set to one.
356#[inline]
357fn init_ctr<Aes>(cipher: Aes, nonce: &cipher::Block<Aes>) -> Ctr32LE<Aes>
358where
359    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
360{
361    let mut counter_block = *nonce;
362    counter_block[15] |= 0x80;
363    Ctr32LE::inner_iv_init(cipher, &counter_block)
364}