use aes_gcm::aead::rand_core::RngCore; use aes_gcm::{
aead::{AeadInPlace, KeyInit, Nonce, OsRng, Tag}, Aes256Gcm,
Key,
};
use pbkdf2::pbkdf2_hmac;
use sha2::Sha256;
use std::io::{Read, Write};
pub const KEY_LEN: usize = 32; pub const SALT_LEN: usize = 16;
pub const IV_LEN: usize = 12; pub const TAG_LEN: usize = 16; pub const DEFAULT_CHUNK_SIZE: usize = 16 * 1024 * 1024;
pub fn derive_key(password: &[u8], salt: &[u8]) -> Key<Aes256Gcm> {
let mut key_bytes = [0u8; KEY_LEN];
pbkdf2_hmac::<Sha256>(password, salt, 100_000, &mut key_bytes); key_bytes.into() }
pub fn encrypt_stream<R: Read, W: Write>(
reader: &mut R,
writer: &mut W,
key: &Key<Aes256Gcm>,
initial_nonce: &[u8; IV_LEN], aad: &[u8], chunk_size: usize,
) -> Result<usize, Box<dyn std::error::Error>> {
let cipher = Aes256Gcm::new(key);
let mut total_bytes_written = 0;
let mut plaintext_buffer = vec![0; chunk_size];
let fixed_iv_part: [u8; 4] = initial_nonce[0..4].try_into().unwrap(); let mut counter_bytes = [0u8; 8];
counter_bytes.copy_from_slice(&initial_nonce[4..12]);
let mut current_nonce_value = u64::from_be_bytes(counter_bytes);
loop {
let bytes_read = reader.read(&mut plaintext_buffer)?;
if bytes_read == 0 {
break; }
let mut full_nonce_bytes = [0u8; IV_LEN];
full_nonce_bytes[0..4].copy_from_slice(&fixed_iv_part); full_nonce_bytes[4..12].copy_from_slice(¤t_nonce_value.to_be_bytes());
let nonce = Nonce::<Aes256Gcm>::from_slice(&full_nonce_bytes);
let tag = cipher
.encrypt_in_place_detached(
nonce,
aad, &mut plaintext_buffer[..bytes_read], )
.map_err(|e| {
Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error>
})?;
writer.write_all(&plaintext_buffer[..bytes_read])?; writer.write_all(tag.as_ref())?; total_bytes_written += bytes_read + TAG_LEN;
current_nonce_value += 1;
}
Ok(total_bytes_written)
}
pub fn decrypt_stream<R: Read, W: Write>(
reader: &mut R,
writer: &mut W,
key: &Key<Aes256Gcm>,
initial_nonce: &[u8; IV_LEN], aad: &[u8], chunk_size: usize,
) -> Result<usize, Box<dyn std::error::Error>> {
let cipher = Aes256Gcm::new(key);
let mut total_bytes_written = 0;
let mut plaintext_buffer = vec![0; chunk_size];
let mut ciphertext_read_buffer = vec![0; chunk_size + TAG_LEN];
let fixed_iv_part: [u8; 4] = initial_nonce[0..4].try_into().unwrap(); let mut counter_bytes = [0u8; 8];
counter_bytes.copy_from_slice(&initial_nonce[4..12]);
let mut current_nonce_value = u64::from_be_bytes(counter_bytes);
loop {
let bytes_read_combined = reader.read(&mut ciphertext_read_buffer[..])?;
if bytes_read_combined == 0 {
break; }
if bytes_read_combined < TAG_LEN {
return Err("Truncated data: encrypted chunk too short to contain a tag".into());
}
let current_ciphertext_len = bytes_read_combined - TAG_LEN;
let mut tag_buffer = [0u8; TAG_LEN];
tag_buffer
.copy_from_slice(&ciphertext_read_buffer[current_ciphertext_len..bytes_read_combined]);
plaintext_buffer[..current_ciphertext_len]
.copy_from_slice(&ciphertext_read_buffer[..current_ciphertext_len]);
let mut full_nonce_bytes = [0u8; IV_LEN];
full_nonce_bytes[0..4].copy_from_slice(&fixed_iv_part); full_nonce_bytes[4..12].copy_from_slice(¤t_nonce_value.to_be_bytes());
let nonce = Nonce::<Aes256Gcm>::from_slice(&full_nonce_bytes);
cipher
.decrypt_in_place_detached(
nonce,
aad, &mut plaintext_buffer[..current_ciphertext_len], Tag::<Aes256Gcm>::from_slice(&tag_buffer), )
.map_err(|e| {
Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Decryption error: {}", e),
)) as Box<dyn std::error::Error>
})?;
writer.write_all(&plaintext_buffer[..current_ciphertext_len])?; total_bytes_written += current_ciphertext_len;
current_nonce_value += 1;
}
Ok(total_bytes_written)
}
pub fn generate_random_bytes<const N: usize>() -> [u8; N] {
let mut bytes = [0u8; N];
OsRng.fill_bytes(&mut bytes);
bytes
}