Skip to main content

aes_gcm/
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
9//! # Usage
10//!
11//! Simple usage (allocating, no associated data):
12//!
13#![cfg_attr(feature = "getrandom", doc = "```")]
14#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
15//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
16//! // NOTE: requires the `getrandom` feature is enabled
17//!
18//! use aes_gcm::{
19//!     aead::{Aead, AeadCore, Generate, Key, KeyInit},
20//!     Aes256Gcm, Nonce, // Or `Aes128Gcm`
21//! };
22//!
23//! let key = Key::<Aes256Gcm>::generate();
24//! let cipher = Aes256Gcm::new(&key);
25//!
26//! let nonce = Nonce::generate(); // MUST be unique per message
27//! let ciphertext = cipher.encrypt(&nonce, b"plaintext message".as_ref())?;
28//!
29//! let plaintext = cipher.decrypt(&nonce, ciphertext.as_ref())?;
30//! assert_eq!(&plaintext, b"plaintext message");
31//! # Ok(())
32//! # }
33//! ```
34//!
35//! ## In-place Usage (eliminates `alloc` requirement)
36//!
37//! This crate has an optional `alloc` feature which can be disabled in e.g.
38//! microcontroller environments that don't have a heap.
39//!
40//! The [`AeadInOut::encrypt_in_place`] and [`AeadInOut::decrypt_in_place`]
41//! methods accept any type that impls the [`aead::Buffer`] trait which
42//! contains the plaintext for encryption or ciphertext for decryption.
43//!
44//! Enabling the `arrayvec` feature of this crate will provide an impl of
45//! [`aead::Buffer`] for `arrayvec::ArrayVec` (re-exported from the [`aead`] crate as
46//! [`aead::arrayvec::ArrayVec`]), and enabling the `bytes` feature of this crate will
47//! provide an impl of [`aead::Buffer`] for `bytes::BytesMut` (re-exported from the
48//! [`aead`] crate as [`aead::bytes::BytesMut`]).
49//!
50//! It can then be passed as the `buffer` parameter to the in-place encrypt
51//! and decrypt methods:
52//!
53#![cfg_attr(all(feature = "getrandom", feature = "arrayvec"), doc = "```")]
54#![cfg_attr(
55    not(all(feature = "getrandom", feature = "arrayvec")),
56    doc = "```ignore"
57)]
58//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
59//! // NOTE: requires the `arrayvec` and `getrandom` features are enabled
60//!
61//! use aes_gcm::{
62//!     aead::{AeadCore, AeadInOut, Generate, Key, KeyInit, arrayvec::ArrayVec},
63//!     Aes256Gcm, Nonce, // Or `Aes128Gcm`
64//! };
65//!
66//! let key = Key::<Aes256Gcm>::generate();
67//! let cipher = Aes256Gcm::new(&key);
68//!
69//! let nonce = Nonce::generate(); // MUST be unique per message
70//! let mut buffer: ArrayVec<u8, 128> = ArrayVec::new(); // Note: buffer needs 16-bytes overhead for auth tag
71//! buffer.try_extend_from_slice(b"plaintext message").unwrap();
72//!
73//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
74//! cipher.encrypt_in_place(&nonce, b"", &mut buffer)?;
75//!
76//! // `buffer` now contains the message ciphertext
77//! assert_ne!(buffer.as_ref(), b"plaintext message");
78//!
79//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
80//! cipher.decrypt_in_place(&nonce, b"", &mut buffer)?;
81//! assert_eq!(buffer.as_ref(), b"plaintext message");
82//! # Ok(())
83//! # }
84
85pub use aead::{self, AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser};
86
87#[cfg(feature = "aes")]
88pub use aes;
89
90use aead::{TagPosition, inout::InOutBuf};
91
92use cipher::{
93    BlockCipherEncrypt, BlockSizeUser, InnerIvInit, StreamCipherCore,
94    array::{Array, ArraySize},
95    consts::U16,
96};
97use core::{fmt, marker::PhantomData};
98use ghash::{GHash, universal_hash::UniversalHash};
99
100#[cfg(feature = "zeroize")]
101use zeroize::Zeroize;
102
103#[cfg(feature = "aes")]
104use aes::{Aes128, Aes256, cipher::consts::U12};
105
106/// Maximum length of associated data in bytes.
107pub const A_MAX: u64 = (1 << 61) - 1;
108
109/// Maximum length of plaintext in bytes.
110pub const P_MAX: u64 = (1 << 36) - 32;
111
112/// AES-GCM nonces.
113pub type Nonce<NonceSize> = Array<u8, NonceSize>;
114
115/// AES-GCM tags.
116pub type Tag<TagSize = U16> = Array<u8, TagSize>;
117
118/// Trait implemented for valid tag sizes, i.e.
119/// [`U12`][consts::U12], [`U13`][consts::U13], [`U14`][consts::U14],
120/// [`U15`][consts::U15] and [`U16`][consts::U16].
121/// When the crate feature `hazmat` is enabled, [`U4`][consts::U4] and
122/// [`U8`][consts::U8] are also supported.
123pub trait TagSize: private::SealedTagSize {}
124
125impl<T: private::SealedTagSize> TagSize for T {}
126
127mod private {
128    use cipher::{array::ArraySize, consts, typenum::Unsigned};
129
130    // Sealed traits stop other crates from implementing any traits that use it.
131    pub trait SealedTagSize: ArraySize + Unsigned {}
132
133    #[cfg(feature = "hazmat")]
134    impl SealedTagSize for consts::U4 {}
135    #[cfg(feature = "hazmat")]
136    impl SealedTagSize for consts::U8 {}
137
138    impl SealedTagSize for consts::U12 {}
139    impl SealedTagSize for consts::U13 {}
140    impl SealedTagSize for consts::U14 {}
141    impl SealedTagSize for consts::U15 {}
142    impl SealedTagSize for consts::U16 {}
143}
144
145/// AES-GCM with a 128-bit key and 96-bit nonce.
146#[cfg(feature = "aes")]
147#[cfg_attr(docsrs, doc(cfg(feature = "aes")))]
148pub type Aes128Gcm = AesGcm<Aes128, U12>;
149
150/// AES-GCM with a 256-bit key and 96-bit nonce.
151#[cfg(feature = "aes")]
152#[cfg_attr(docsrs, doc(cfg(feature = "aes")))]
153pub type Aes256Gcm = AesGcm<Aes256, U12>;
154
155/// AES block.
156type Block = Array<u8, U16>;
157
158/// Counter mode with a 32-bit big endian counter.
159type Ctr32BE<Aes> = ctr::CtrCore<Aes, ctr::flavors::Ctr32BE>;
160
161/// AES-GCM: generic over an underlying AES implementation and nonce size.
162///
163/// This type is generic to support substituting alternative AES implementations
164/// (e.g. embedded hardware implementations)
165///
166/// It is NOT intended to be instantiated with any block cipher besides AES!
167/// Doing so runs the risk of unintended cryptographic properties!
168///
169/// The `NonceSize` generic parameter can be used to instantiate AES-GCM with other
170/// nonce sizes, however it's recommended to use it with `typenum::U12`,
171/// the default of 96-bits.
172///
173/// The `TagSize` generic parameter can be used to instantiate AES-GCM with other
174/// authorization tag sizes, however it's recommended to use it with `typenum::U16`,
175/// the default of 128-bits.
176///
177/// If in doubt, use the built-in [`Aes128Gcm`] and [`Aes256Gcm`] type aliases.
178///
179/// # ⚠️ WARNING: Hazmat!
180///
181/// When using short authentication tags, namely 32-bit tags with `typenum::U4` or
182/// 64-bit tags with `typenum::U8` (which require the crate feature `hazmat`), it is
183/// **RECOMMENDED** that a key not be used for more than the maximum invocations of
184/// authenticated decryption specified in Table 1 or Table 2 of NIST SP 800-38D,
185/// respectively.
186#[derive(Clone)]
187pub struct AesGcm<Aes, NonceSize, TagSize = U16>
188where
189    TagSize: crate::TagSize,
190{
191    /// Encryption cipher.
192    cipher: Aes,
193
194    /// GHASH authenticator.
195    ghash: GHash,
196
197    /// Length of the nonce.
198    nonce_size: PhantomData<NonceSize>,
199
200    /// Length of the tag.
201    tag_size: PhantomData<TagSize>,
202}
203
204impl<Aes, NonceSize, TagSize> KeySizeUser for AesGcm<Aes, NonceSize, TagSize>
205where
206    Aes: KeySizeUser,
207    TagSize: crate::TagSize,
208{
209    type KeySize = Aes::KeySize;
210}
211
212impl<Aes, NonceSize, TagSize> KeyInit for AesGcm<Aes, NonceSize, TagSize>
213where
214    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + KeyInit,
215    TagSize: crate::TagSize,
216{
217    fn new(key: &Key<Self>) -> Self {
218        Aes::new(key).into()
219    }
220}
221
222impl<Aes, NonceSize, TagSize> From<Aes> for AesGcm<Aes, NonceSize, TagSize>
223where
224    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
225    TagSize: crate::TagSize,
226{
227    fn from(cipher: Aes) -> Self {
228        let mut ghash_key = ghash::Key::default();
229        cipher.encrypt_block(&mut ghash_key);
230
231        let ghash = GHash::new(&ghash_key);
232
233        #[cfg(feature = "zeroize")]
234        ghash_key.zeroize();
235
236        Self {
237            cipher,
238            ghash,
239            nonce_size: PhantomData,
240            tag_size: PhantomData,
241        }
242    }
243}
244
245impl<Aes, NonceSize, TagSize> AeadCore for AesGcm<Aes, NonceSize, TagSize>
246where
247    NonceSize: ArraySize,
248    TagSize: crate::TagSize,
249{
250    type NonceSize = NonceSize;
251    type TagSize = TagSize;
252    const TAG_POSITION: TagPosition = TagPosition::Postfix;
253}
254
255impl<Aes, NonceSize, TagSize> AeadInOut for AesGcm<Aes, NonceSize, TagSize>
256where
257    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
258    NonceSize: ArraySize,
259    TagSize: crate::TagSize,
260{
261    fn encrypt_inout_detached(
262        &self,
263        nonce: &Nonce<NonceSize>,
264        associated_data: &[u8],
265        mut buffer: InOutBuf<'_, '_, u8>,
266    ) -> Result<Tag<TagSize>, Error> {
267        if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
268            return Err(Error);
269        }
270
271        let (ctr, mask) = self.init_ctr(nonce);
272
273        // TODO(tarcieri): interleave encryption with GHASH
274        // See: <https://github.com/RustCrypto/AEADs/issues/74>
275        ctr.apply_keystream_partial(buffer.reborrow());
276
277        let full_tag = self.compute_tag(mask, associated_data, buffer.get_out());
278        full_tag[..TagSize::to_usize()]
279            .try_into()
280            .map_err(|_| Error)
281    }
282
283    fn decrypt_inout_detached(
284        &self,
285        nonce: &Nonce<NonceSize>,
286        associated_data: &[u8],
287        buffer: InOutBuf<'_, '_, u8>,
288        tag: &Tag<TagSize>,
289    ) -> Result<(), Error> {
290        if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
291            return Err(Error);
292        }
293
294        let (ctr, mask) = self.init_ctr(nonce);
295
296        // TODO(tarcieri): interleave encryption with GHASH
297        // See: <https://github.com/RustCrypto/AEADs/issues/74>
298        let expected_tag = self.compute_tag(mask, associated_data, buffer.get_in());
299
300        use subtle::ConstantTimeEq;
301        if expected_tag[..TagSize::to_usize()].ct_eq(tag).into() {
302            ctr.apply_keystream_partial(buffer);
303            Ok(())
304        } else {
305            Err(Error)
306        }
307    }
308}
309
310impl<Aes, NonceSize, TagSize> AesGcm<Aes, NonceSize, TagSize>
311where
312    Aes: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
313    NonceSize: ArraySize,
314    TagSize: crate::TagSize,
315{
316    /// Initialize counter mode.
317    ///
318    /// See algorithm described in Section 7.2 of NIST SP800-38D:
319    /// <https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf>
320    ///
321    /// > Define a block, J0, as follows:
322    /// > If len(IV)=96, then J0 = IV || 0{31} || 1.
323    /// > If len(IV) ≠ 96, then let s = 128 ⎡len(IV)/128⎤-len(IV), and
324    /// >     J0=GHASH(IV||0s+64||[len(IV)]64).
325    fn init_ctr(&self, nonce: &Nonce<NonceSize>) -> (Ctr32BE<&Aes>, Block) {
326        let j0 = if NonceSize::to_usize() == 12 {
327            let mut block = ghash::Block::default();
328            block[..12].copy_from_slice(nonce);
329            block[15] = 1;
330            block
331        } else {
332            let mut ghash = self.ghash.clone();
333            ghash.update_padded(nonce);
334
335            let mut block = ghash::Block::default();
336            let nonce_bits = (NonceSize::to_usize() as u64) * 8;
337            block[8..].copy_from_slice(&nonce_bits.to_be_bytes());
338            ghash.update(&[block]);
339            ghash.finalize()
340        };
341
342        let mut ctr = Ctr32BE::inner_iv_init(&self.cipher, &j0);
343        let mut tag_mask = Block::default();
344        ctr.write_keystream_block(&mut tag_mask);
345        (ctr, tag_mask)
346    }
347
348    /// Authenticate the given plaintext and associated data using GHASH.
349    fn compute_tag(&self, mask: Block, associated_data: &[u8], buffer: &[u8]) -> Tag {
350        let mut ghash = self.ghash.clone();
351        ghash.update_padded(associated_data);
352        ghash.update_padded(buffer);
353
354        let associated_data_bits = (associated_data.len() as u64) * 8;
355        let buffer_bits = (buffer.len() as u64) * 8;
356
357        let mut block = ghash::Block::default();
358        block[..8].copy_from_slice(&associated_data_bits.to_be_bytes());
359        block[8..].copy_from_slice(&buffer_bits.to_be_bytes());
360        ghash.update(&[block]);
361
362        let mut tag = ghash.finalize();
363        for (a, b) in tag.as_mut_slice().iter_mut().zip(mask.as_slice()) {
364            *a ^= *b;
365        }
366
367        tag
368    }
369}
370
371impl<Aes, NonceSize, TagSize> fmt::Debug for AesGcm<Aes, NonceSize, TagSize>
372where
373    TagSize: crate::TagSize,
374{
375    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
376        f.debug_struct("AesGcm").finish_non_exhaustive()
377    }
378}