use crate::errors::{CrabError, CrabResult};
use aes_gcm::{
aead::{
stream::{DecryptorBE32, EncryptorBE32, Nonce as StreamNonce, StreamBE32},
Key, KeyInit,
},
Aes256Gcm,
};
use chacha20poly1305::ChaCha20Poly1305 as ChaCha20Poly1305Cipher;
type AesStreamNonce = StreamNonce<Aes256Gcm, StreamBE32<Aes256Gcm>>;
type ChaChaStreamNonce = StreamNonce<ChaCha20Poly1305Cipher, StreamBE32<ChaCha20Poly1305Cipher>>;
pub const DEFAULT_CHUNK_SIZE: usize = 64 * 1024;
pub const MAX_CHUNK_SIZE: usize = 1024 * 1024;
pub struct Aes256GcmStreamEncryptor {
encryptor: EncryptorBE32<Aes256Gcm>,
nonce: Vec<u8>,
}
impl Aes256GcmStreamEncryptor {
pub fn new(key: &[u8]) -> CrabResult<Self> {
if key.len() != 32 {
return Err(CrabError::invalid_input(format!(
"AES-256 requires 32-byte key, got {}",
key.len()
)));
}
let nonce_bytes = crate::rand::secure_bytes(7)?;
let nonce_array: &AesStreamNonce = nonce_bytes.as_slice().into();
let key_array: &Key<Aes256Gcm> = key.into();
let aead = Aes256Gcm::new(key_array);
let encryptor = EncryptorBE32::from_aead(aead, nonce_array);
Ok(Self {
encryptor,
nonce: nonce_bytes,
})
}
pub fn nonce(&self) -> Vec<u8> {
self.nonce.clone()
}
pub fn encrypt_next(&mut self, plaintext: &[u8]) -> CrabResult<Vec<u8>> {
self.encryptor
.encrypt_next(plaintext)
.map_err(|e| CrabError::crypto_error(format!("Stream encryption failed: {}", e)))
}
pub fn encrypt_last(self, plaintext: &[u8]) -> CrabResult<Vec<u8>> {
self.encryptor
.encrypt_last(plaintext)
.map_err(|e| CrabError::crypto_error(format!("Stream encryption failed: {}", e)))
}
}
pub struct Aes256GcmStreamDecryptor {
decryptor: DecryptorBE32<Aes256Gcm>,
}
impl Aes256GcmStreamDecryptor {
pub fn from_nonce(key: &[u8], nonce: &[u8]) -> CrabResult<Self> {
if key.len() != 32 {
return Err(CrabError::invalid_input(format!(
"AES-256 requires 32-byte key, got {}",
key.len()
)));
}
if nonce.len() != 7 {
return Err(CrabError::invalid_input(format!(
"Invalid nonce size for STREAM: expected 7, got {}",
nonce.len()
)));
}
let nonce_array: &AesStreamNonce = nonce.into();
let key_array: &Key<Aes256Gcm> = key.into();
let aead = Aes256Gcm::new(key_array);
let decryptor = DecryptorBE32::from_aead(aead, nonce_array);
Ok(Self { decryptor })
}
pub fn decrypt_next(&mut self, ciphertext: &[u8]) -> CrabResult<Vec<u8>> {
self.decryptor
.decrypt_next(ciphertext)
.map_err(|e| CrabError::crypto_error(format!("Stream decryption failed: {}", e)))
}
pub fn decrypt_last(self, ciphertext: &[u8]) -> CrabResult<Vec<u8>> {
self.decryptor
.decrypt_last(ciphertext)
.map_err(|e| CrabError::crypto_error(format!("Stream decryption failed: {}", e)))
}
}
pub struct ChaCha20Poly1305StreamEncryptor {
encryptor: EncryptorBE32<ChaCha20Poly1305Cipher>,
nonce: Vec<u8>,
}
impl ChaCha20Poly1305StreamEncryptor {
pub fn new(key: &[u8]) -> CrabResult<Self> {
if key.len() != 32 {
return Err(CrabError::invalid_input(format!(
"ChaCha20-Poly1305 requires 32-byte key, got {}",
key.len()
)));
}
let nonce_bytes = crate::rand::secure_bytes(7)?;
let nonce_array: &ChaChaStreamNonce = nonce_bytes.as_slice().into();
let key_array: &Key<ChaCha20Poly1305Cipher> = key.into();
let aead = ChaCha20Poly1305Cipher::new(key_array);
let encryptor = EncryptorBE32::from_aead(aead, nonce_array);
Ok(Self {
encryptor,
nonce: nonce_bytes,
})
}
pub fn nonce(&self) -> Vec<u8> {
self.nonce.clone()
}
pub fn encrypt_next(&mut self, plaintext: &[u8]) -> CrabResult<Vec<u8>> {
self.encryptor
.encrypt_next(plaintext)
.map_err(|e| CrabError::crypto_error(format!("Stream encryption failed: {}", e)))
}
pub fn encrypt_last(self, plaintext: &[u8]) -> CrabResult<Vec<u8>> {
self.encryptor
.encrypt_last(plaintext)
.map_err(|e| CrabError::crypto_error(format!("Stream encryption failed: {}", e)))
}
}
pub struct ChaCha20Poly1305StreamDecryptor {
decryptor: DecryptorBE32<ChaCha20Poly1305Cipher>,
}
impl ChaCha20Poly1305StreamDecryptor {
pub fn from_nonce(key: &[u8], nonce: &[u8]) -> CrabResult<Self> {
if key.len() != 32 {
return Err(CrabError::invalid_input(format!(
"ChaCha20-Poly1305 requires 32-byte key, got {}",
key.len()
)));
}
if nonce.len() != 7 {
return Err(CrabError::invalid_input(format!(
"Invalid nonce size for STREAM: expected 7, got {}",
nonce.len()
)));
}
let nonce_array: &ChaChaStreamNonce = nonce.into();
let key_array: &Key<ChaCha20Poly1305Cipher> = key.into();
let aead = ChaCha20Poly1305Cipher::new(key_array);
let decryptor = DecryptorBE32::from_aead(aead, nonce_array);
Ok(Self { decryptor })
}
pub fn decrypt_next(&mut self, ciphertext: &[u8]) -> CrabResult<Vec<u8>> {
self.decryptor
.decrypt_next(ciphertext)
.map_err(|e| CrabError::crypto_error(format!("Stream decryption failed: {}", e)))
}
pub fn decrypt_last(self, ciphertext: &[u8]) -> CrabResult<Vec<u8>> {
self.decryptor
.decrypt_last(ciphertext)
.map_err(|e| CrabError::crypto_error(format!("Stream decryption failed: {}", e)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_aes256gcm_stream_encrypt_decrypt() -> CrabResult<()> {
let key = crate::rand::secure_bytes(32)?;
let plaintext_chunks = vec![b"Hello, ".as_slice(), b"world!".as_slice()];
let mut encryptor = Aes256GcmStreamEncryptor::new(&key)?;
let nonce = encryptor.nonce();
let mut ciphertext_chunks = Vec::new();
for chunk in &plaintext_chunks {
ciphertext_chunks.push(encryptor.encrypt_next(chunk)?);
}
ciphertext_chunks.push(encryptor.encrypt_last(b"")?);
let mut decryptor = Aes256GcmStreamDecryptor::from_nonce(&key, &nonce)?;
let mut decrypted = Vec::new();
let last_idx = ciphertext_chunks.len() - 1;
for (i, chunk) in ciphertext_chunks.iter().enumerate() {
if i == last_idx {
decrypted.extend_from_slice(&decryptor.decrypt_last(chunk)?);
break; } else {
decrypted.extend_from_slice(&decryptor.decrypt_next(chunk)?);
}
}
let expected: Vec<u8> = plaintext_chunks.concat();
assert_eq!(decrypted, expected);
Ok(())
}
#[test]
fn test_chacha20poly1305_stream_encrypt_decrypt() -> CrabResult<()> {
let key = crate::rand::secure_bytes(32)?;
let plaintext_chunks = vec![b"First chunk".as_slice(), b"Second chunk".as_slice()];
let mut encryptor = ChaCha20Poly1305StreamEncryptor::new(&key)?;
let nonce = encryptor.nonce();
let mut ciphertext_chunks = Vec::new();
for chunk in &plaintext_chunks {
ciphertext_chunks.push(encryptor.encrypt_next(chunk)?);
}
ciphertext_chunks.push(encryptor.encrypt_last(b"Final chunk")?);
let mut decryptor = ChaCha20Poly1305StreamDecryptor::from_nonce(&key, &nonce)?;
let mut decrypted = Vec::new();
let last_idx = ciphertext_chunks.len() - 1;
for (i, chunk) in ciphertext_chunks.iter().enumerate() {
if i == last_idx {
decrypted.extend_from_slice(&decryptor.decrypt_last(chunk)?);
break; } else {
decrypted.extend_from_slice(&decryptor.decrypt_next(chunk)?);
}
}
let mut expected: Vec<u8> = plaintext_chunks.concat();
expected.extend_from_slice(b"Final chunk");
assert_eq!(decrypted, expected);
Ok(())
}
#[test]
fn test_stream_large_data() -> CrabResult<()> {
let key = crate::rand::secure_bytes(32)?;
let chunk_size = 1024;
let num_chunks = 100;
let mut plaintext = Vec::new();
for i in 0..num_chunks {
plaintext.extend_from_slice(&vec![i as u8; chunk_size]);
}
let mut encryptor = Aes256GcmStreamEncryptor::new(&key)?;
let nonce = encryptor.nonce();
let mut ciphertext_chunks = Vec::new();
for chunk in plaintext.chunks(chunk_size) {
ciphertext_chunks.push(encryptor.encrypt_next(chunk)?);
}
ciphertext_chunks.push(encryptor.encrypt_last(&[])?);
let mut decryptor = Aes256GcmStreamDecryptor::from_nonce(&key, &nonce)?;
let mut decrypted = Vec::new();
let last_idx = ciphertext_chunks.len() - 1;
for (i, chunk) in ciphertext_chunks.iter().enumerate() {
if i == last_idx {
decrypted.extend_from_slice(&decryptor.decrypt_last(chunk)?);
break; } else {
decrypted.extend_from_slice(&decryptor.decrypt_next(chunk)?);
}
}
assert_eq!(decrypted, plaintext);
Ok(())
}
#[test]
fn test_stream_authentication_failure() -> CrabResult<()> {
let key = crate::rand::secure_bytes(32)?;
let mut encryptor = Aes256GcmStreamEncryptor::new(&key)?;
let nonce = encryptor.nonce();
let chunk1 = encryptor.encrypt_next(b"Hello")?;
let mut chunk2 = encryptor.encrypt_last(b"World")?;
chunk2[0] ^= 1;
let mut decryptor = Aes256GcmStreamDecryptor::from_nonce(&key, &nonce)?;
decryptor.decrypt_next(&chunk1)?;
let result = decryptor.decrypt_last(&chunk2);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_stream_empty_chunks() -> CrabResult<()> {
let key = crate::rand::secure_bytes(32)?;
let encryptor = Aes256GcmStreamEncryptor::new(&key)?;
let nonce = encryptor.nonce();
let chunk = encryptor.encrypt_last(b"")?;
let decryptor = Aes256GcmStreamDecryptor::from_nonce(&key, &nonce)?;
let decrypted = decryptor.decrypt_last(&chunk)?;
assert_eq!(decrypted, b"");
Ok(())
}
}