use aes_gcm::{
Aes256Gcm, Key, KeyInit, Nonce,
aead::{Aead, Payload},
};
use std::io::{Read, Write};
use crate::{Error, Gcm256Key, GcmNonce, MAX_CHUNK_SIZE, TAG_LEN};
fn counter_to_bytes(counter: u64) -> [u8; 12] {
let mut bytes = [0u8; 12];
bytes[4..].copy_from_slice(&counter.to_be_bytes());
bytes
}
fn chunk_nonce(root: &GcmNonce, counter: u64) -> GcmNonce {
let offset = counter_to_bytes(counter);
let mut bytes = *root.as_slice();
bytes
.iter_mut()
.zip(offset.iter())
.for_each(|(b, o)| *b ^= *o);
GcmNonce::from_slice(&bytes)
}
fn chunk_aad(counter: u64, is_final: bool) -> [u8; 13] {
let mut aad = [0u8; 13];
aad[..12].copy_from_slice(&counter_to_bytes(counter));
aad[12] = u8::from(is_final);
aad
}
fn fill_chunk<R: Read>(reader: &mut R, mut buf: Vec<u8>) -> Result<Vec<u8>, Error> {
let capacity = buf.len();
let mut filled = 0;
while filled < capacity {
let n = reader.read(&mut buf[filled..])?;
if n == 0 {
break;
}
filled += n;
}
buf.truncate(filled);
Ok(buf)
}
pub fn encrypt_stream<R: Read, W: Write>(
reader: &mut R,
writer: &mut W,
key: &Gcm256Key,
root_nonce: &GcmNonce,
chunk_size: u32,
) -> Result<(), Error> {
let cipher = Aes256Gcm::new(&Key::<Aes256Gcm>::from(*key.as_slice()));
let size = chunk_size as usize;
let mut counter: u64 = 0;
let mut current = fill_chunk(reader, vec![0u8; size])?;
loop {
let next = fill_chunk(reader, vec![0u8; size])?;
let is_final = next.is_empty();
let nonce = chunk_nonce(root_nonce, counter);
let aad = chunk_aad(counter, is_final);
let sealed = cipher.encrypt(
&Nonce::from(*nonce.as_slice()),
Payload {
msg: ¤t,
aad: &aad,
},
)?;
writer.write_all(&sealed)?;
counter += 1;
if is_final {
break;
}
current = next;
}
Ok(())
}
pub fn decrypt_stream<R: Read, W: Write>(
reader: &mut R,
writer: &mut W,
key: &Gcm256Key,
root_nonce: &GcmNonce,
chunk_size: u32,
) -> Result<(), Error> {
if chunk_size == 0 || chunk_size > MAX_CHUNK_SIZE {
return Err(Error::InvalidChunkSize);
}
let cipher = Aes256Gcm::new(&Key::<Aes256Gcm>::from(*key.as_slice()));
let size = chunk_size as usize + TAG_LEN;
let mut counter: u64 = 0;
let mut current = fill_chunk(reader, vec![0u8; size])?;
loop {
let next = fill_chunk(reader, vec![0u8; size])?;
let is_final = next.is_empty();
let nonce = chunk_nonce(root_nonce, counter);
let aad = chunk_aad(counter, is_final);
let plaintext = cipher.decrypt(
&Nonce::from(*nonce.as_slice()),
Payload {
msg: ¤t,
aad: &aad,
},
)?;
writer.write_all(&plaintext)?;
counter += 1;
if is_final {
break;
}
current = next;
}
Ok(())
}