blob: 1a03cf693cd47464137d92fc5c36cfdcb76960a1 [file] [log] [blame]
//! AES-GCM: [Authenticated Encryption and Associated Data (AEAD)][1] cipher
//! based on AES in [Galois/Counter Mode][2].
//!
//! ## Performance Notes
//!
//! By default this crate will use software implementations of both AES and
//! the POLYVAL universal hash function.
//!
//! When targeting modern x86/x86_64 CPUs, use the following `RUSTFLAGS` to
//! take advantage of high performance AES-NI and CLMUL CPU intrinsics:
//!
//! ```text
//! RUSTFLAGS="-Ctarget-cpu=sandybridge -Ctarget-feature=+aes,+sse2,+sse4.1,+ssse3"
//! ```
//!
//! ## Security Notes
//!
//! This crate has received one [security audit by NCC Group][3], with no significant
//! findings. We would like to thank [MobileCoin][4] for funding the audit.
//!
//! All implementations contained in the crate are designed to execute in constant
//! time, either by relying on hardware intrinsics (i.e. AES-NI and CLMUL on
//! x86/x86_64), or using a portable implementation which is only constant time
//! on processors which implement constant-time multiplication.
//!
//! It is not suitable for use on processors with a variable-time multiplication
//! operation (e.g. short circuit on multiply-by-zero / multiply-by-one, such as
//! certain 32-bit PowerPC CPUs and some non-ARM microcontrollers).
//!
//! # Usage
//!
//! Simple usage (allocating, no associated data):
//!
//! ```
//! use aes_gcm::{Aes256Gcm, Key, Nonce}; // Or `Aes128Gcm`
//! use aes_gcm::aead::{Aead, NewAead};
//!
//! let key = Key::from_slice(b"an example very very secret key.");
//! let cipher = Aes256Gcm::new(key);
//!
//! let nonce = Nonce::from_slice(b"unique nonce"); // 96-bits; unique per message
//!
//! let ciphertext = cipher.encrypt(nonce, b"plaintext message".as_ref())
//! .expect("encryption failure!"); // NOTE: handle this error to avoid panics!
//!
//! let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())
//! .expect("decryption failure!"); // NOTE: handle this error to avoid panics!
//!
//! assert_eq!(&plaintext, b"plaintext message");
//! ```
//!
//! ## In-place Usage (eliminates `alloc` requirement)
//!
//! This crate has an optional `alloc` feature which can be disabled in e.g.
//! microcontroller environments that don't have a heap.
//!
//! The [`AeadInPlace::encrypt_in_place`] and [`AeadInPlace::decrypt_in_place`]
//! methods accept any type that impls the [`aead::Buffer`] trait which
//! contains the plaintext for encryption or ciphertext for decryption.
//!
//! Note that if you enable the `heapless` feature of this crate,
//! you will receive an impl of [`aead::Buffer`] for `heapless::Vec`
//! (re-exported from the [`aead`] crate as [`aead::heapless::Vec`]),
//! which can then be passed as the `buffer` parameter to the in-place encrypt
//! and decrypt methods:
//!
//! ```
//! # #[cfg(feature = "heapless")]
//! # {
//! use aes_gcm::{Aes256Gcm, Key, Nonce}; // Or `Aes128Gcm`
//! use aes_gcm::aead::{AeadInPlace, NewAead};
//! use aes_gcm::aead::heapless::Vec;
//!
//! let key = Key::from_slice(b"an example very very secret key.");
//! let cipher = Aes256Gcm::new(key);
//!
//! let nonce = Nonce::from_slice(b"unique nonce"); // 96-bits; unique per message
//!
//! let mut buffer: Vec<u8, 128> = Vec::new(); // Buffer needs 16-bytes overhead for GCM tag
//! buffer.extend_from_slice(b"plaintext message");
//!
//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
//! cipher.encrypt_in_place(nonce, b"", &mut buffer).expect("encryption failure!");
//!
//! // `buffer` now contains the message ciphertext
//! assert_ne!(&buffer, b"plaintext message");
//!
//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
//! cipher.decrypt_in_place(nonce, b"", &mut buffer).expect("decryption failure!");
//! assert_eq!(&buffer, b"plaintext message");
//! # }
//! ```
//!
//! [1]: https://en.wikipedia.org/wiki/Authenticated_encryption
//! [2]: https://en.wikipedia.org/wiki/Galois/Counter_Mode
//! [3]: https://research.nccgroup.com/2020/02/26/public-report-rustcrypto-aes-gcm-and-chacha20poly1305-implementation-review/
//! [4]: https://www.mobilecoin.com/
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
)]
#![deny(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms)]
pub use aead::{self, AeadCore, AeadInPlace, Error, NewAead};
#[cfg(feature = "aes")]
pub use aes;
use cipher::{
consts::{U0, U16},
generic_array::{typenum::Unsigned, ArrayLength, GenericArray},
Block, BlockCipher, BlockCipherKey, BlockEncrypt, FromBlockCipher, NewBlockCipher,
StreamCipher, StreamCipherSeek,
};
use core::marker::PhantomData;
use ctr::Ctr32BE;
use ghash::{
universal_hash::{NewUniversalHash, UniversalHash},
GHash,
};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
#[cfg(feature = "aes")]
use aes::{cipher::consts::U12, Aes128, Aes256};
/// Maximum length of associated data
pub const A_MAX: u64 = 1 << 36;
/// Maximum length of plaintext
pub const P_MAX: u64 = 1 << 36;
/// Maximum length of ciphertext
pub const C_MAX: u64 = (1 << 36) + 16;
/// AES-GCM keys
pub type Key<KeySize> = GenericArray<u8, KeySize>;
/// AES-GCM nonces
pub type Nonce<NonceSize> = GenericArray<u8, NonceSize>;
/// AES-GCM tags
pub type Tag = GenericArray<u8, U16>;
/// AES-GCM with a 128-bit key and 96-bit nonce
#[cfg(feature = "aes")]
#[cfg_attr(docsrs, doc(cfg(feature = "aes")))]
pub type Aes128Gcm = AesGcm<Aes128, U12>;
/// AES-GCM with a 256-bit key and 96-bit nonce
#[cfg(feature = "aes")]
#[cfg_attr(docsrs, doc(cfg(feature = "aes")))]
pub type Aes256Gcm = AesGcm<Aes256, U12>;
/// AES-GCM: generic over an underlying AES implementation and nonce size.
///
/// This type is generic to support substituting alternative AES implementations
/// (e.g. embedded hardware implementations)
///
/// It is NOT intended to be instantiated with any block cipher besides AES!
/// Doing so runs the risk of unintended cryptographic properties!
///
/// The `N` generic parameter can be used to instantiate AES-GCM with other
/// nonce sizes, however it's recommended to use it with `typenum::U12`,
/// the default of 96-bits.
///
/// If in doubt, use the built-in [`Aes128Gcm`] and [`Aes256Gcm`] type aliases.
#[derive(Clone)]
pub struct AesGcm<Aes, NonceSize>
where
Aes: BlockCipher<BlockSize = U16> + BlockEncrypt,
Aes::ParBlocks: ArrayLength<Block<Aes>>,
NonceSize: ArrayLength<u8>,
{
/// Encryption cipher
cipher: Aes,
/// GHASH authenticator
ghash: GHash,
/// Length of the nonce
nonce_size: PhantomData<NonceSize>,
}
impl<Aes, NonceSize> NewAead for AesGcm<Aes, NonceSize>
where
Aes: NewBlockCipher + BlockCipher<BlockSize = U16> + BlockEncrypt,
Aes::ParBlocks: ArrayLength<Block<Aes>>,
NonceSize: ArrayLength<u8>,
{
type KeySize = Aes::KeySize;
fn new(key: &BlockCipherKey<Aes>) -> Self {
Aes::new(key).into()
}
}
impl<Aes, NonceSize> From<Aes> for AesGcm<Aes, NonceSize>
where
Aes: NewBlockCipher + BlockCipher<BlockSize = U16> + BlockEncrypt,
Aes::ParBlocks: ArrayLength<Block<Aes>>,
NonceSize: ArrayLength<u8>,
{
fn from(cipher: Aes) -> Self {
let mut ghash_key = ghash::Key::default();
cipher.encrypt_block(&mut ghash_key);
let ghash = GHash::new(&ghash_key);
#[cfg(feature = "zeroize")]
ghash_key.zeroize();
Self {
cipher,
ghash,
nonce_size: PhantomData,
}
}
}
impl<Aes, NonceSize> AeadCore for AesGcm<Aes, NonceSize>
where
Aes: NewBlockCipher + BlockCipher<BlockSize = U16> + BlockEncrypt,
Aes::ParBlocks: ArrayLength<Block<Aes>>,
NonceSize: ArrayLength<u8>,
{
type NonceSize = NonceSize;
type TagSize = U16;
type CiphertextOverhead = U0;
}
impl<Aes, NonceSize> AeadInPlace for AesGcm<Aes, NonceSize>
where
Aes: NewBlockCipher + BlockCipher<BlockSize = U16> + BlockEncrypt,
Aes::ParBlocks: ArrayLength<Block<Aes>>,
NonceSize: ArrayLength<u8>,
{
fn encrypt_in_place_detached(
&self,
nonce: &Nonce<Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
) -> Result<Tag, Error> {
if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX {
return Err(Error);
}
// TODO(tarcieri): interleave encryption with GHASH
// See: <https://github.com/RustCrypto/AEADs/issues/74>
let mut ctr = self.init_ctr(nonce);
ctr.seek(Aes::BlockSize::to_usize());
ctr.apply_keystream(buffer);
let mut tag = self.compute_tag(associated_data, buffer);
ctr.seek(0);
ctr.apply_keystream(tag.as_mut_slice());
Ok(tag)
}
fn decrypt_in_place_detached(
&self,
nonce: &Nonce<Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
tag: &Tag,
) -> Result<(), Error> {
if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX {
return Err(Error);
}
// TODO(tarcieri): interleave encryption with GHASH
// See: <https://github.com/RustCrypto/AEADs/issues/74>
let mut expected_tag = self.compute_tag(associated_data, buffer);
let mut ctr = self.init_ctr(nonce);
ctr.apply_keystream(expected_tag.as_mut_slice());
use subtle::ConstantTimeEq;
if expected_tag.ct_eq(&tag).unwrap_u8() == 1 {
ctr.apply_keystream(buffer);
Ok(())
} else {
Err(Error)
}
}
}
impl<Aes, NonceSize> AesGcm<Aes, NonceSize>
where
Aes: NewBlockCipher + BlockCipher<BlockSize = U16> + BlockEncrypt,
Aes::ParBlocks: ArrayLength<Block<Aes>>,
NonceSize: ArrayLength<u8>,
{
/// Initialize counter mode.
///
/// See algorithm described in Section 7.2 of NIST SP800-38D:
/// <https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf>
///
/// > Define a block, J0, as follows:
/// > If len(IV)=96, then J0 = IV || 0{31} || 1.
/// > If len(IV) ≠ 96, then let s = 128 ⎡len(IV)/128⎤-len(IV), and
/// > J0=GHASH(IV||0s+64||[len(IV)]64).
fn init_ctr(&self, nonce: &Nonce<NonceSize>) -> Ctr32BE<&Aes> {
let j0 = if NonceSize::to_usize() == 12 {
let mut block = ghash::Block::default();
block[..12].copy_from_slice(nonce);
block[15] = 1;
block
} else {
let mut ghash = self.ghash.clone();
ghash.update_padded(nonce);
let mut block = ghash::Block::default();
let nonce_bits = (NonceSize::to_usize() as u64) * 8;
block[8..].copy_from_slice(&nonce_bits.to_be_bytes());
ghash.update(&block);
ghash.finalize().into_bytes()
};
Ctr32BE::from_block_cipher(&self.cipher, &j0)
}
/// Authenticate the given plaintext and associated data using GHASH
fn compute_tag(&self, associated_data: &[u8], buffer: &[u8]) -> Tag {
let mut ghash = self.ghash.clone();
ghash.update_padded(associated_data);
ghash.update_padded(buffer);
let associated_data_bits = (associated_data.len() as u64) * 8;
let buffer_bits = (buffer.len() as u64) * 8;
let mut block = ghash::Block::default();
block[..8].copy_from_slice(&associated_data_bits.to_be_bytes());
block[8..].copy_from_slice(&buffer_bits.to_be_bytes());
ghash.update(&block);
ghash.finalize().into_bytes()
}
}