use aws_lc_rs::{
cipher::{
AES_256, DecryptionContext, EncryptingKey, EncryptionContext, StreamingDecryptingKey,
StreamingEncryptingKey, UnboundCipherKey,
},
constant_time,
};
use ghash::{GHash, universal_hash::UniversalHash};
use rand::CryptoRng;
use std::{
io::{BufReader, BufWriter, Read, Write},
ops::DerefMut,
sync::Mutex,
};
use crate::{
IronOxideErr, Result,
crypto::aes::{AES_BLOCK_SIZE, AES_GCM_TAG_LEN, AES_IV_LEN, AES_KEY_LEN},
internal::take_lock,
};
pub(crate) const DEFAULT_IO_BLOCK_SIZE: usize = 64 * 1024;
fn build_counter_block(iv: &[u8; AES_IV_LEN], counter: u32) -> ghash::Block {
let mut block = ghash::Block::default();
block[..AES_IV_LEN].copy_from_slice(iv);
block[AES_IV_LEN..].copy_from_slice(&counter.to_be_bytes());
block
}
fn finalize_gcm_tag(
ghash_acc: GhashAccumulator,
encrypted_j0: &ghash::Block,
ciphertext_len: u64,
) -> [u8; AES_GCM_TAG_LEN] {
let mut ghash = ghash_acc.finalize();
let mut len_block = ghash::Block::default();
len_block[8..].copy_from_slice(&(ciphertext_len * 8).to_be_bytes());
ghash.update(&[len_block]);
let ghash_output = ghash.finalize();
let mut tag = [0u8; AES_GCM_TAG_LEN];
for i in 0..AES_GCM_TAG_LEN {
tag[i] = ghash_output[i] ^ encrypted_j0[i];
}
tag
}
struct GhashAccumulator {
ghash: GHash,
pending: Vec<u8>,
}
impl GhashAccumulator {
fn new(ghash: GHash) -> Self {
Self {
ghash,
pending: Vec::with_capacity(AES_BLOCK_SIZE),
}
}
fn update(&mut self, data: &[u8]) {
self.pending.extend_from_slice(data);
let complete_blocks = self.pending.len() / AES_BLOCK_SIZE;
let complete_len = complete_blocks * AES_BLOCK_SIZE;
for chunk in self.pending[..complete_len].chunks_exact(AES_BLOCK_SIZE) {
let mut block = ghash::Block::default();
block.copy_from_slice(chunk);
self.ghash.update(&[block]);
}
self.pending.drain(..complete_len);
}
fn finalize(mut self) -> GHash {
if !self.pending.is_empty() {
let mut block = ghash::Block::default();
block[..self.pending.len()].copy_from_slice(&self.pending);
self.ghash.update(&[block]);
}
self.ghash
}
}
fn init_gcm_state(key: &[u8; AES_KEY_LEN], iv: &[u8; AES_IV_LEN]) -> Result<(GHash, ghash::Block)> {
let ecb_cipher_key = UnboundCipherKey::new(&AES_256, key)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
let ecb_key = EncryptingKey::ecb(ecb_cipher_key)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
let mut ghash_key = ghash::Key::default();
ecb_key
.encrypt(&mut ghash_key)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
let ghash = GHash::new(&ghash_key);
let mut encrypted_j0 = build_counter_block(iv, 1);
ecb_key
.encrypt(&mut encrypted_j0)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
Ok((ghash, encrypted_j0))
}
pub(crate) struct StreamingEncryptor {
ctr_cipher: StreamingEncryptingKey,
ghash_acc: GhashAccumulator,
encrypted_j0: ghash::Block,
ciphertext_len: u64,
}
impl StreamingEncryptor {
pub(crate) fn new(key: &[u8; AES_KEY_LEN], iv: [u8; AES_IV_LEN]) -> Result<Self> {
let (ghash, encrypted_initial_counter_block) = init_gcm_state(key, &iv)?;
let ctr_iv: [u8; AES_BLOCK_SIZE] = build_counter_block(&iv, 2).into();
let ctr_cipher_key = UnboundCipherKey::new(&AES_256, key)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
let context = EncryptionContext::Iv128(ctr_iv.into());
let ctr_key = StreamingEncryptingKey::less_safe_ctr(ctr_cipher_key, context)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
Ok(Self {
ctr_cipher: ctr_key,
ghash_acc: GhashAccumulator::new(ghash),
encrypted_j0: encrypted_initial_counter_block,
ciphertext_len: 0,
})
}
pub(crate) fn process_chunk(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize> {
if output.len() < input.len() {
return Err(IronOxideErr::FileIoError {
path: None,
operation: "encrypt".to_string(),
message: "Output buffer too small".to_string(),
});
}
let min_out_size = input.len() + AES_BLOCK_SIZE - 1;
let mut temp_output = vec![0u8; min_out_size];
let buffer_update = self
.ctr_cipher
.update(input, &mut temp_output)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
let written = buffer_update.written();
let written_len = written.len();
output[..written_len].copy_from_slice(written);
self.ghash_acc.update(&output[..written_len]);
self.ciphertext_len += written_len as u64;
Ok(written_len)
}
pub(crate) fn finalize(mut self) -> Result<(Vec<u8>, [u8; AES_GCM_TAG_LEN])> {
let mut final_output = vec![0u8; AES_BLOCK_SIZE];
let (_, buffer_update_info) = self
.ctr_cipher
.finish(&mut final_output)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
let remaining = buffer_update_info.written().to_vec();
if !remaining.is_empty() {
self.ghash_acc.update(&remaining);
self.ciphertext_len += remaining.len() as u64;
}
let tag = finalize_gcm_tag(self.ghash_acc, &self.encrypted_j0, self.ciphertext_len);
Ok((remaining, tag))
}
}
pub(crate) struct StreamingDecryptor {
ctr_cipher: StreamingDecryptingKey,
ghash_acc: GhashAccumulator,
ciphertext_len: u64,
encrypted_j0: ghash::Block,
held_back: Vec<u8>,
}
impl StreamingDecryptor {
pub(crate) fn new(key: &[u8; AES_KEY_LEN], iv: [u8; AES_IV_LEN]) -> Result<Self> {
let (ghash, encrypted_j0) = init_gcm_state(key, &iv)?;
let ctr_iv: [u8; AES_BLOCK_SIZE] = build_counter_block(&iv, 2).into();
let ctr_cipher_key = UnboundCipherKey::new(&AES_256, key)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
let context = DecryptionContext::Iv128(ctr_iv.into());
let ctr_key = StreamingDecryptingKey::ctr(ctr_cipher_key, context)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
Ok(Self {
ctr_cipher: ctr_key,
ghash_acc: GhashAccumulator::new(ghash),
encrypted_j0,
held_back: Vec::with_capacity(AES_GCM_TAG_LEN),
ciphertext_len: 0,
})
}
pub(crate) fn process_chunk(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize> {
let mut combined = std::mem::take(&mut self.held_back);
combined.extend_from_slice(input);
if combined.len() <= AES_GCM_TAG_LEN {
self.held_back = combined;
return Ok(0);
}
let to_process_len = combined.len() - AES_GCM_TAG_LEN;
let ciphertext = &combined[..to_process_len];
self.held_back = combined[to_process_len..].to_vec();
if output.len() < ciphertext.len() {
return Err(IronOxideErr::FileIoError {
path: None,
operation: "decrypt".to_string(),
message: "Output buffer too small".to_string(),
});
}
self.ghash_acc.update(ciphertext);
self.ciphertext_len += ciphertext.len() as u64;
let min_out_size = ciphertext.len() + AES_BLOCK_SIZE - 1;
let mut temp_output = vec![0u8; min_out_size];
let buffer_update = self
.ctr_cipher
.update(ciphertext, &mut temp_output)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
let written = buffer_update.written();
let written_len = written.len();
output[..written_len].copy_from_slice(written);
Ok(written_len)
}
pub(crate) fn verify(self) -> Result<Vec<u8>> {
if self.held_back.len() != AES_GCM_TAG_LEN {
return Err(IronOxideErr::AesGcmDecryptError);
}
let expected_tag: [u8; AES_GCM_TAG_LEN] = self
.held_back
.try_into()
.map_err(|_| IronOxideErr::AesGcmDecryptError)?;
let mut final_output = vec![0u8; AES_BLOCK_SIZE];
let buffer_update = self
.ctr_cipher
.finish(&mut final_output)
.map_err(|_| IronOxideErr::AesError(aws_lc_rs::error::Unspecified))?;
let remaining = buffer_update.written().to_vec();
let computed_tag =
finalize_gcm_tag(self.ghash_acc, &self.encrypted_j0, self.ciphertext_len);
constant_time::verify_slices_are_equal(&computed_tag, &expected_tag)
.map_err(|_| IronOxideErr::AesGcmDecryptError)?;
Ok(remaining)
}
}
pub(crate) fn encrypt_stream<R: Read, W: Write, RNG: CryptoRng>(
key: &[u8; AES_KEY_LEN],
rng: &Mutex<RNG>,
reader: &mut BufReader<R>,
writer: &mut BufWriter<W>,
) -> Result<()> {
let mut iv = [0u8; AES_IV_LEN];
take_lock(rng).deref_mut().fill_bytes(&mut iv);
writer
.write_all(&iv)
.map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "write_iv".to_string(),
message: e.to_string(),
})?;
let mut encryptor = StreamingEncryptor::new(key, iv)?;
let mut input_buffer = vec![0u8; DEFAULT_IO_BLOCK_SIZE];
let mut output_buffer = vec![0u8; DEFAULT_IO_BLOCK_SIZE + AES_BLOCK_SIZE];
while let n @ 1.. = reader
.read(&mut input_buffer)
.map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "read".to_string(),
message: e.to_string(),
})?
{
let written = encryptor.process_chunk(&input_buffer[..n], &mut output_buffer)?;
writer
.write_all(&output_buffer[..written])
.map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "write".to_string(),
message: e.to_string(),
})?;
}
let (remaining, tag) = encryptor.finalize()?;
if !remaining.is_empty() {
writer
.write_all(&remaining)
.map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "write".to_string(),
message: e.to_string(),
})?;
}
writer
.write_all(&tag)
.map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "write".to_string(),
message: e.to_string(),
})?;
writer.flush().map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "flush".to_string(),
message: e.to_string(),
})?;
Ok(())
}
pub(crate) fn decrypt_stream<R: Read, W: Write>(
key: &[u8; AES_KEY_LEN],
reader: &mut BufReader<R>,
writer: &mut BufWriter<W>,
) -> Result<()> {
let mut iv = [0u8; AES_IV_LEN];
reader
.read_exact(&mut iv)
.map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "read_iv".to_string(),
message: e.to_string(),
})?;
let mut decryptor = StreamingDecryptor::new(key, iv)?;
let mut input_buffer = vec![0u8; DEFAULT_IO_BLOCK_SIZE];
let mut output_buffer = vec![0u8; DEFAULT_IO_BLOCK_SIZE + AES_BLOCK_SIZE + AES_GCM_TAG_LEN];
while let n @ 1.. = reader
.read(&mut input_buffer)
.map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "read".to_string(),
message: e.to_string(),
})?
{
let written = decryptor.process_chunk(&input_buffer[..n], &mut output_buffer)?;
if written > 0 {
writer
.write_all(&output_buffer[..written])
.map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "write".to_string(),
message: e.to_string(),
})?;
}
}
let remaining_plaintext = decryptor.verify()?;
if !remaining_plaintext.is_empty() {
writer
.write_all(&remaining_plaintext)
.map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "write".to_string(),
message: e.to_string(),
})?;
}
writer.flush().map_err(|e| IronOxideErr::FileIoError {
path: None,
operation: "flush".to_string(),
message: e.to_string(),
})?;
Ok(())
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::crypto::aes;
use proptest::prelude::*;
use rand::{Rng, SeedableRng, rngs::StdRng};
use std::io::Cursor;
pub fn generate_test_key() -> [u8; AES_KEY_LEN] {
let mut key = [0u8; AES_KEY_LEN];
rand::rng().fill_bytes(&mut key);
key
}
pub fn test_rng() -> Mutex<StdRng> {
Mutex::new(StdRng::from_rng(&mut rand::rng()))
}
#[test]
fn test_streaming_encrypt_decrypt_roundtrip() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = b"deadbeef";
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext);
}
#[test]
fn test_streaming_encrypt_decrypt_large_data() {
let key = generate_test_key();
let rng = test_rng();
let mut plaintext = vec![0u8; 1024 * 1024];
rand::rng().fill_bytes(&mut plaintext);
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(&plaintext));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
let expected_len = AES_IV_LEN + plaintext.len() + AES_GCM_TAG_LEN;
assert_eq!(ciphertext_buf.len(), expected_len);
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext);
}
#[test]
fn test_streaming_decrypt_detects_tampered_ciphertext() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = b"deadbeef";
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
ciphertext_buf[AES_IV_LEN] ^= 0xFF;
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
let result = decrypt_stream(&key, &mut reader, &mut writer);
assert!(result.is_err());
}
#[test]
fn test_streaming_decrypt_detects_tampered_tag() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = b"Hello, World!";
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
let last_idx = ciphertext_buf.len() - 1;
ciphertext_buf[last_idx] ^= 0xFF;
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
let result = decrypt_stream(&key, &mut reader, &mut writer);
assert!(result.is_err());
}
#[test]
fn test_interop_with_aes_module_decrypt_streaming_encrypted() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = b"Test data for interop";
let mut ciphertext_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
let mut aes_value: aes::AesEncryptedValue = ciphertext_buf.as_slice().try_into().unwrap();
let decrypted = aes::decrypt(&mut aes_value, key).unwrap();
assert_eq!(decrypted, plaintext.as_slice());
}
#[test]
fn test_interop_with_aes_module_streaming_decrypt_aes_encrypted() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = b"Test data for interop";
let mut decrypted_buf = Vec::new();
let encrypted = aes::encrypt(&rng, plaintext.to_vec(), key).unwrap();
let encrypted_bytes = encrypted.bytes();
{
let mut reader = BufReader::new(Cursor::new(encrypted_bytes));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext);
}
#[test]
fn test_interop_large_data_streaming_encrypt_standard_decrypt() {
let key = generate_test_key();
let rng = test_rng();
let mut ciphertext_buf = Vec::new();
let mut plaintext = vec![0u8; 1024 * 1024];
rand::rng().fill_bytes(&mut plaintext);
{
let mut reader = BufReader::new(Cursor::new(&plaintext));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
let mut aes_value: aes::AesEncryptedValue = ciphertext_buf.as_slice().try_into().unwrap();
let decrypted = aes::decrypt(&mut aes_value, key).unwrap();
assert_eq!(decrypted, plaintext.as_slice());
}
#[test]
fn test_interop_large_data_standard_encrypt_streaming_decrypt() {
let key = generate_test_key();
let rng = test_rng();
let mut decrypted_buf = Vec::new();
let mut plaintext = vec![0u8; 1024 * 1024];
rand::rng().fill_bytes(&mut plaintext);
let encrypted = aes::encrypt(&rng, plaintext.clone(), key).unwrap();
let encrypted_bytes = encrypted.bytes();
{
let mut reader = BufReader::new(Cursor::new(encrypted_bytes));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext);
}
#[test]
fn test_empty_plaintext() {
let key = generate_test_key();
let rng = test_rng();
let plaintext: &[u8] = &[];
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
assert_eq!(ciphertext_buf.len(), AES_IV_LEN + AES_GCM_TAG_LEN);
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf.len(), 0);
}
#[test]
fn test_single_byte_plaintext() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = &[42u8];
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext);
}
#[test]
fn test_exact_block_boundary_16_bytes() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = [0xABu8; 16];
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext);
}
#[test]
fn test_exact_block_boundary_32_bytes() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = [0xCDu8; 32];
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext);
}
#[test]
fn test_exact_block_boundary_48_bytes() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = [0xEFu8; 48];
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext);
}
#[test]
fn test_one_over_block_boundary_17_bytes() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = [0x12u8; 17];
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext);
}
#[test]
fn test_one_under_block_boundary_15_bytes() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = [0x34u8; 15];
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext);
}
#[test]
fn test_wrong_key_fails_decryption() {
let encrypt_key = generate_test_key();
let decrypt_key = generate_test_key(); let rng = test_rng();
let plaintext = b"Data encrypted with one key, decrypted with another";
let mut ciphertext_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&encrypt_key, &rng, &mut reader, &mut writer).unwrap();
}
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut decrypted_buf = Vec::new();
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
let result = decrypt_stream(&decrypt_key, &mut reader, &mut writer);
assert!(result.is_err());
}
#[test]
fn test_truncated_ciphertext_fails() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = b"Some data to encrypt";
let mut ciphertext_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
ciphertext_buf.truncate(ciphertext_buf.len() - 5);
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut decrypted_buf = Vec::new();
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
let result = decrypt_stream(&key, &mut reader, &mut writer);
assert!(result.is_err());
}
#[test]
fn test_truncated_to_just_iv_fails() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = b"Some data";
let mut ciphertext_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
ciphertext_buf.truncate(AES_IV_LEN);
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut decrypted_buf = Vec::new();
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
let result = decrypt_stream(&key, &mut reader, &mut writer);
assert!(result.is_err());
}
#[test]
fn test_truncated_missing_partial_tag_fails() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = b"Test data for partial tag truncation";
let mut ciphertext_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
ciphertext_buf.truncate(ciphertext_buf.len() - 8);
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut decrypted_buf = Vec::new();
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
let result = decrypt_stream(&key, &mut reader, &mut writer);
assert!(result.is_err());
}
#[test]
fn test_iv_modification_fails() {
let key = generate_test_key();
let rng = test_rng();
let plaintext = b"Test IV modification detection";
let mut ciphertext_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(plaintext.as_slice()));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
ciphertext_buf[0] ^= 0xFF;
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut decrypted_buf = Vec::new();
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
let result = decrypt_stream(&key, &mut reader, &mut writer);
assert!(result.is_err());
}
#[test]
fn test_various_sizes_near_io_block_boundary() {
let key = generate_test_key();
let rng = test_rng();
let test_sizes = [
DEFAULT_IO_BLOCK_SIZE - 1,
DEFAULT_IO_BLOCK_SIZE,
DEFAULT_IO_BLOCK_SIZE + 1,
DEFAULT_IO_BLOCK_SIZE * 2 - 1,
DEFAULT_IO_BLOCK_SIZE * 2,
DEFAULT_IO_BLOCK_SIZE * 2 + 1,
];
for size in test_sizes {
let plaintext: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(&plaintext));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
assert_eq!(decrypted_buf, plaintext, "Failed for size {}", size);
}
}
#[test]
fn test_ghash_naive_chunking_concrete() {
let data = [1u8, 2, 3, 4, 5, 6];
let key = [
0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34,
0x2b, 0x2e,
];
let ghash_key = ghash::Key::from(key);
let output_correct = {
let mut ghash = GHash::new(&ghash_key);
let mut block = ghash::Block::default();
block[..6].copy_from_slice(&data);
ghash.update(&[block]);
ghash.finalize()
};
let output_wrong = {
let mut ghash = GHash::new(&ghash_key);
let mut block1 = ghash::Block::default();
block1[..3].copy_from_slice(&data[..3]);
ghash.update(&[block1]);
let mut block2 = ghash::Block::default();
block2[..3].copy_from_slice(&data[3..]);
ghash.update(&[block2]);
ghash.finalize()
};
assert_ne!(
output_correct.as_slice(),
output_wrong.as_slice(),
"Naive chunking should produce different result!"
);
}
proptest! {
#[test]
fn prop_streaming_roundtrip(plaintext in prop::collection::vec(any::<u8>(), 0..100_000)) {
let key = generate_test_key();
let rng = test_rng();
let mut ciphertext_buf = Vec::new();
let mut decrypted_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(&plaintext));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
{
let mut reader = BufReader::new(Cursor::new(&ciphertext_buf));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
prop_assert_eq!(plaintext, decrypted_buf);
}
#[test]
fn prop_streaming_interop_standard_decrypt(plaintext in prop::collection::vec(any::<u8>(), 0..50_000)) {
let key = generate_test_key();
let rng = test_rng();
let mut ciphertext_buf = Vec::new();
{
let mut reader = BufReader::new(Cursor::new(&plaintext));
let mut writer = BufWriter::new(Cursor::new(&mut ciphertext_buf));
encrypt_stream(&key, &rng, &mut reader, &mut writer).unwrap();
}
let mut aes_value: aes::AesEncryptedValue = ciphertext_buf.as_slice().try_into().unwrap();
let decrypted = aes::decrypt(&mut aes_value, key).unwrap();
prop_assert_eq!(&plaintext[..], decrypted);
}
#[test]
fn prop_standard_encrypt_streaming_decrypt(plaintext in prop::collection::vec(any::<u8>(), 0..50_000)) {
let key = generate_test_key();
let rng = test_rng();
let mut decrypted_buf = Vec::new();
let encrypted = aes::encrypt(&rng, plaintext.clone(), key).unwrap();
let encrypted_bytes = encrypted.bytes();
{
let mut reader = BufReader::new(Cursor::new(encrypted_bytes));
let mut writer = BufWriter::new(Cursor::new(&mut decrypted_buf));
decrypt_stream(&key, &mut reader, &mut writer).unwrap();
}
prop_assert_eq!(plaintext, decrypted_buf);
}
#[test]
fn prop_ghash_accumulator_chunking_invariant(data in prop::collection::vec(any::<u8>(), 1..5000), chunk_sizes in prop::collection::vec(1..100usize, 1..50)) {
let key = [
0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b,
0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34, 0x2b, 0x2e,
];
let ghash_key = ghash::Key::from(key);
let mut acc_single = GhashAccumulator::new(GHash::new(&ghash_key));
acc_single.update(&data);
let ghash_single = acc_single.finalize();
let mut acc_chunked = GhashAccumulator::new(GHash::new(&ghash_key));
let mut offset = 0;
for &size in &chunk_sizes {
if offset >= data.len() { break; }
let end = (offset + size).min(data.len());
acc_chunked.update(&data[offset..end]);
offset = end;
}
if offset < data.len() {
acc_chunked.update(&data[offset..]);
}
let ghash_chunked = acc_chunked.finalize();
let block = ghash::Block::default();
let output_single = {
let mut g = ghash_single;
g.update(&[block]);
g.finalize()
};
let output_chunked = {
let mut g = ghash_chunked;
g.update(&[block]);
g.finalize()
};
prop_assert_eq!(output_single.as_slice(), output_chunked.as_slice());
}
}
}