diff --git a/aes-gcm/src/ctr.rs b/aes-gcm/src/ctr.rs index 20be3e9f..63d6035d 100644 --- a/aes-gcm/src/ctr.rs +++ b/aes-gcm/src/ctr.rs @@ -1,7 +1,7 @@ //! Counter mode implementation use block_cipher_trait::generic_array::{ - typenum::{Unsigned, U12, U16}, + typenum::{Unsigned, U16}, ArrayLength, GenericArray, }; use block_cipher_trait::BlockCipher; @@ -27,6 +27,9 @@ where /// Current CTR value counter_block: Block128, + + /// Base value of the counter + base_counter: u32, } impl Ctr32 @@ -35,22 +38,25 @@ where B::ParBlocks: ArrayLength>, { /// Instantiate a new CTR instance - pub fn new(nonce: &GenericArray) -> Self { - let mut counter_block = GenericArray::default(); - counter_block[..12].copy_from_slice(nonce.as_slice()); - counter_block[15] = 1; + pub fn new(j0: Block128) -> Self { + let base_counter = u32::from_be_bytes(j0[12..].try_into().unwrap()); Self { block_cipher: PhantomData, buffer: unsafe { mem::zeroed() }, - counter_block, + counter_block: j0, + base_counter, } } /// "Seek" to the given NIST SP800-38D counter value. Note that the /// serialized big endian value is 1 larger than the provided "counter value" pub fn seek(&mut self, new_counter_value: u32) { - self.counter_block[12..].copy_from_slice(&new_counter_value.wrapping_add(1).to_be_bytes()); + self.counter_block[12..].copy_from_slice( + &new_counter_value + .wrapping_add(self.base_counter) + .to_be_bytes(), + ); } /// Apply AES-CTR keystream to the given input buffer diff --git a/aes-gcm/src/lib.rs b/aes-gcm/src/lib.rs index 6a928ddf..2d7b0c8e 100644 --- a/aes-gcm/src/lib.rs +++ b/aes-gcm/src/lib.rs @@ -101,18 +101,22 @@ mod ctr; pub use aead; +#[cfg(feature = "aes")] +pub use aes; + use self::ctr::Ctr32; use aead::{Aead, Error, NewAead}; use block_cipher_trait::generic_array::{ - typenum::{U0, U12, U16}, + typenum::{U0, U16}, ArrayLength, GenericArray, }; use block_cipher_trait::BlockCipher; +use core::marker::PhantomData; use ghash::{universal_hash::UniversalHash, GHash}; use zeroize::Zeroize; #[cfg(feature = "aes")] -use aes::{Aes128, Aes256}; +use aes::{block_cipher_trait::generic_array::typenum::U12, Aes128, Aes256}; /// Maximum length of associated data pub const A_MAX: u64 = 1 << 36; @@ -126,32 +130,37 @@ pub const C_MAX: u64 = (1 << 36) + 16; /// AES-GCM tags pub type Tag = GenericArray; -/// AES-GCM with a 128-bit key +/// AES-GCM with a 128-bit key and 96-bit nonce #[cfg(feature = "aes")] -pub type Aes128Gcm = AesGcm; +pub type Aes128Gcm = AesGcm; -/// AES-GCM with a 256-bit key +/// AES-GCM with a 256-bit key and 96-bit nonce #[cfg(feature = "aes")] -pub type Aes256Gcm = AesGcm; +pub type Aes256Gcm = AesGcm; /// AES-GCM #[derive(Clone)] -pub struct AesGcm +pub struct AesGcm where B: BlockCipher, B::ParBlocks: ArrayLength>, + N: ArrayLength, { /// Encryption cipher cipher: B, /// GHASH authenticator ghash: GHash, + + /// Length of the nonce + nonce_size: PhantomData, } -impl NewAead for AesGcm +impl NewAead for AesGcm where B: BlockCipher, B::ParBlocks: ArrayLength>, + N: ArrayLength, { type KeySize = B::KeySize; @@ -162,10 +171,11 @@ where } } -impl From for AesGcm +impl From for AesGcm where B: BlockCipher, B::ParBlocks: ArrayLength>, + N: ArrayLength, { fn from(cipher: B) -> Self { let mut ghash_key = GenericArray::default(); @@ -174,22 +184,27 @@ where let ghash = GHash::new(&ghash_key); ghash_key.zeroize(); - Self { cipher, ghash } + Self { + cipher, + ghash, + nonce_size: PhantomData, + } } } -impl Aead for AesGcm +impl Aead for AesGcm where B: BlockCipher, B::ParBlocks: ArrayLength>, + N: ArrayLength, { - type NonceSize = U12; + type NonceSize = N; type TagSize = U16; type CiphertextOverhead = U0; fn encrypt_in_place_detached( &self, - nonce: &GenericArray, + nonce: &GenericArray, associated_data: &[u8], buffer: &mut [u8], ) -> Result { @@ -199,11 +214,11 @@ where // TODO(tarcieri): interleave encryption with GHASH // See: - let mut ctr = Ctr32::new(nonce); + let mut ctr = self.init_ctr(nonce); ctr.seek(1); ctr.apply_keystream(&self.cipher, buffer); - let mut tag = compute_tag(&mut self.ghash.clone(), associated_data, buffer); + let mut tag = self.compute_tag(associated_data, buffer); ctr.seek(0); ctr.apply_keystream(&self.cipher, tag.as_mut_slice()); @@ -212,7 +227,7 @@ where fn decrypt_in_place_detached( &self, - nonce: &GenericArray, + nonce: &GenericArray, associated_data: &[u8], buffer: &mut [u8], tag: &Tag, @@ -223,8 +238,8 @@ where // TODO(tarcieri): interleave encryption with GHASH // See: - let mut expected_tag = compute_tag(&mut self.ghash.clone(), associated_data, buffer); - let mut ctr = Ctr32::new(nonce); + let mut expected_tag = self.compute_tag(associated_data, buffer); + let mut ctr = self.init_ctr(nonce); ctr.apply_keystream(&self.cipher, expected_tag.as_mut_slice()); use subtle::ConstantTimeEq; @@ -237,18 +252,56 @@ where } } -/// Authenticate the given plaintext and associated data using GHASH -fn compute_tag(ghash: &mut GHash, associated_data: &[u8], buffer: &[u8]) -> Tag { - ghash.update_padded(associated_data); - ghash.update_padded(buffer); +impl AesGcm +where + B: BlockCipher, + B::ParBlocks: ArrayLength>, + N: ArrayLength, +{ + /// Initialize counter mode. + /// + /// See algorithm described in Section 7.2 of NIST SP800-38D: + /// + /// + /// > 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: &GenericArray) -> Ctr32 { + let j0 = if N::to_usize() == 12 { + let mut block = GenericArray::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 = GenericArray::default(); + let nonce_bits = (N::to_usize() as u64) * 8; + block[8..].copy_from_slice(&nonce_bits.to_be_bytes()); + ghash.update_block(&block); - let associated_data_bits = (associated_data.len() as u64) * 8; - let buffer_bits = (buffer.len() as u64) * 8; + ghash.result().into_bytes() + }; + + Ctr32::new(j0) + } - let mut block = GenericArray::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(&block); + /// 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); - ghash.result_reset().into_bytes() + let associated_data_bits = (associated_data.len() as u64) * 8; + let buffer_bits = (buffer.len() as u64) * 8; + + let mut block = GenericArray::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(&block); + + ghash.result().into_bytes() + } } diff --git a/aes-gcm/tests/other_ivlen.rs b/aes-gcm/tests/other_ivlen.rs new file mode 100644 index 00000000..9e3a8c86 --- /dev/null +++ b/aes-gcm/tests/other_ivlen.rs @@ -0,0 +1,81 @@ +//! Tests for AES-GCM when used with non-96-bit IVs. +//! +//! Vectors taken from NIST CAVS vectors' `gcmEncryptExtIV128.rsp` file +/// + +#[macro_use] +extern crate hex_literal; + +use aes_gcm::{ + aead::{ + generic_array::{typenum, GenericArray}, + Aead, NewAead, + }, + aes::Aes128, + AesGcm, +}; + +/// Based on the following `gcmEncryptExtIV128.rsp` test vector: +/// +/// [Keylen = 128] +/// [IVlen = 8] +/// [PTlen = 128] +/// [AADlen = 0] +/// [Taglen = 128] +/// +/// Count = 0 +mod ivlen8 { + use super::*; + + type Aes128GcmWith8BitNonce = AesGcm; + + #[test] + fn encrypt() { + let key = hex!("15b2d414826453f9e1c7dd0b69d8d1eb"); + let nonce = hex!("b6"); + let plaintext = hex!("8cfa255530c6fbc19d51bd4aeb39c91b"); + + let ciphertext = Aes128GcmWith8BitNonce::new(key.into()) + .encrypt(GenericArray::from_slice(&nonce), &plaintext[..]) + .unwrap(); + + let (ct, tag) = ciphertext.split_at(ciphertext.len() - 16); + assert_eq!(hex!("4822cb98bd5f5d921ee19285c9032375"), ct); + assert_eq!(hex!("8a40670ebac98cf4e9cc1bf8f803167d"), tag); + } +} + +/// Based on the following `gcmEncryptExtIV128.rsp` test vector: +/// +/// [Keylen = 128] +/// [IVlen = 1024] +/// [PTlen = 128] +/// [AADlen = 0] +/// [Taglen = 128] +/// +/// Count = 0 +mod ivlen1024 { + use super::*; + + type Aes128GcmWith1024BitNonce = AesGcm; + + #[test] + fn encrypt() { + let key = hex!("71eebc49c8fb773b2224eaff3ad68714"); + let nonce = hex!( + "07e961e67784011f72faafd95b0eb64089c8de15ad685ec57e63d56e679d3e20 + 2b18b75fcbbec3185ffc41653bc2ac4ae6ae8be8c85636f353a9d19a86100d0b + d035cc6bdefcab4318ac7b1a08b819427ad8f6abc782466c6ebd4d6a0dd76e78 + 389b0a2a66506bb85f038ffc1da220c24f3817c7b2d02c5e8fc5e7e3be5074bc" + ); + let plaintext = hex!("705da82292143d2c949dc4ba014f6396"); + + let ciphertext = Aes128GcmWith1024BitNonce::new(key.into()) + .encrypt(GenericArray::from_slice(&nonce), &plaintext[..]) + .unwrap(); + + let (ct, tag) = ciphertext.split_at(ciphertext.len() - 16); + assert_eq!(hex!("032363cf0828a03553478bec0f51f372"), ct); + assert_eq!(hex!("c681b2c568feaa21900bc44b86aeb946"), tag); + } +}