Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions aes-gcm/src/ctr.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -27,6 +27,9 @@ where

/// Current CTR value
counter_block: Block128,

/// Base value of the counter
base_counter: u32,
}

impl<B> Ctr32<B>
Expand All @@ -35,22 +38,25 @@ where
B::ParBlocks: ArrayLength<GenericArray<u8, B::BlockSize>>,
{
/// Instantiate a new CTR instance
pub fn new(nonce: &GenericArray<u8, U12>) -> 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
Expand Down
111 changes: 82 additions & 29 deletions aes-gcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -126,32 +130,37 @@ pub const C_MAX: u64 = (1 << 36) + 16;
/// AES-GCM tags
pub type Tag = GenericArray<u8, U16>;

/// 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<Aes128>;
pub type Aes128Gcm = AesGcm<Aes128, U12>;

/// 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<Aes256>;
pub type Aes256Gcm = AesGcm<Aes256, U12>;

/// AES-GCM
#[derive(Clone)]
pub struct AesGcm<B>
pub struct AesGcm<B, N>
where
B: BlockCipher<BlockSize = U16>,
B::ParBlocks: ArrayLength<GenericArray<u8, B::BlockSize>>,
N: ArrayLength<u8>,
{
/// Encryption cipher
cipher: B,

/// GHASH authenticator
ghash: GHash,

/// Length of the nonce
nonce_size: PhantomData<N>,
}

impl<B> NewAead for AesGcm<B>
impl<B, N> NewAead for AesGcm<B, N>
where
B: BlockCipher<BlockSize = U16>,
B::ParBlocks: ArrayLength<GenericArray<u8, B::BlockSize>>,
N: ArrayLength<u8>,
{
type KeySize = B::KeySize;

Expand All @@ -162,10 +171,11 @@ where
}
}

impl<B> From<B> for AesGcm<B>
impl<B, N> From<B> for AesGcm<B, N>
where
B: BlockCipher<BlockSize = U16>,
B::ParBlocks: ArrayLength<GenericArray<u8, B::BlockSize>>,
N: ArrayLength<u8>,
{
fn from(cipher: B) -> Self {
let mut ghash_key = GenericArray::default();
Expand All @@ -174,22 +184,27 @@ where
let ghash = GHash::new(&ghash_key);
ghash_key.zeroize();

Self { cipher, ghash }
Self {
cipher,
ghash,
nonce_size: PhantomData,
}
}
}

impl<B> Aead for AesGcm<B>
impl<B, N> Aead for AesGcm<B, N>
where
B: BlockCipher<BlockSize = U16>,
B::ParBlocks: ArrayLength<GenericArray<u8, B::BlockSize>>,
N: ArrayLength<u8>,
{
type NonceSize = U12;
type NonceSize = N;
type TagSize = U16;
type CiphertextOverhead = U0;

fn encrypt_in_place_detached(
&self,
nonce: &GenericArray<u8, Self::NonceSize>,
nonce: &GenericArray<u8, N>,
associated_data: &[u8],
buffer: &mut [u8],
) -> Result<Tag, Error> {
Expand All @@ -199,11 +214,11 @@ where

// TODO(tarcieri): interleave encryption with GHASH
// See: <https://github.com/RustCrypto/AEADs/issues/74>
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());

Expand All @@ -212,7 +227,7 @@ where

fn decrypt_in_place_detached(
&self,
nonce: &GenericArray<u8, Self::NonceSize>,
nonce: &GenericArray<u8, N>,
associated_data: &[u8],
buffer: &mut [u8],
tag: &Tag,
Expand All @@ -223,8 +238,8 @@ where

// TODO(tarcieri): interleave encryption with GHASH
// See: <https://github.com/RustCrypto/AEADs/issues/74>
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;
Expand All @@ -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<B, N> AesGcm<B, N>
where
B: BlockCipher<BlockSize = U16>,
B::ParBlocks: ArrayLength<GenericArray<u8, B::BlockSize>>,
N: 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: &GenericArray<u8, N>) -> Ctr32<B> {
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()
}
}
81 changes: 81 additions & 0 deletions aes-gcm/tests/other_ivlen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//! Tests for AES-GCM when used with non-96-bit IVs.
//!
//! Vectors taken from NIST CAVS vectors' `gcmEncryptExtIV128.rsp` file
/// <https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/CAVP-TESTING-BLOCK-CIPHER-MODES>

#[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<Aes128, typenum::U1>;

#[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<Aes128, typenum::U128>;

#[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);
}
}