use crate::cipher::mode::{get_block_size, infer_cipher_mode, CipherMode};
use crate::cipher::pkcs7::Pkcs7Padding;
use crate::error::{CryptoError, Result};
use crate::key::Key;
use crate::random::SecureRandom;
use crate::side_channel::{protect_critical_operation, SideChannelConfig, SideChannelContext};
use crate::types::Algorithm;
use std::sync::{Arc, Mutex};
pub struct StreamingCipher {
algorithm: Algorithm,
key: Option<Key>,
context: Option<Arc<Mutex<SideChannelContext>>>,
buffer: Vec<u8>,
chunk_size: usize,
is_initialized: bool,
total_processed: usize,
nonce: Option<Vec<u8>>,
#[allow(dead_code)]
tag_buffer: Vec<u8>,
encrypt_mode: Option<bool>,
#[allow(dead_code)]
cipher_mode: CipherMode,
enable_padding: bool,
}
impl StreamingCipher {
pub fn new(algorithm: Algorithm, chunk_size: usize) -> Result<Self> {
match algorithm {
Algorithm::AES128GCM
| Algorithm::AES192GCM
| Algorithm::AES256GCM
| Algorithm::SM4GCM => {}
_ => {
return Err(CryptoError::InvalidParameter(format!(
"Algorithm {:?} does not support streaming encryption",
algorithm
)))
}
}
let context = Arc::new(Mutex::new(SideChannelContext::new(
SideChannelConfig::default(),
)));
let cipher_mode = infer_cipher_mode(algorithm);
let enable_padding = cipher_mode.requires_padding();
Ok(Self {
algorithm,
key: None,
context: Some(context),
buffer: Vec::with_capacity(chunk_size * 2),
chunk_size,
is_initialized: false,
total_processed: 0,
nonce: None,
tag_buffer: Vec::new(),
encrypt_mode: None,
cipher_mode,
enable_padding,
})
}
#[allow(dead_code)]
pub fn is_encrypting(&self) -> bool {
self.encrypt_mode.unwrap_or(true)
}
pub fn with_side_channel_config(
algorithm: Algorithm,
chunk_size: usize,
config: SideChannelConfig,
) -> Result<Self> {
match algorithm {
Algorithm::AES128GCM
| Algorithm::AES192GCM
| Algorithm::AES256GCM
| Algorithm::SM4GCM => {}
_ => {
return Err(CryptoError::InvalidParameter(format!(
"Algorithm {:?} does not support streaming encryption",
algorithm
)))
}
}
let context = Arc::new(Mutex::new(SideChannelContext::new(config)));
let cipher_mode = infer_cipher_mode(algorithm);
let enable_padding = cipher_mode.requires_padding();
Ok(Self {
algorithm,
key: None,
context: Some(context),
buffer: Vec::with_capacity(chunk_size * 2),
chunk_size,
is_initialized: false,
total_processed: 0,
nonce: None,
tag_buffer: Vec::new(),
encrypt_mode: None,
cipher_mode,
enable_padding,
})
}
#[allow(dead_code)]
pub fn initialize(&mut self, key: Key, nonce: Option<Vec<u8>>) -> Result<()> {
if self.is_initialized {
return Err(CryptoError::InvalidState(
"Streaming cipher already initialized".into(),
));
}
if !self.is_key_compatible(&key) {
return Err(CryptoError::InvalidParameter(
"Key algorithm mismatch".into(),
));
}
let final_nonce = match nonce {
Some(n) => {
if n.len() != 12 {
return Err(CryptoError::InvalidParameter(
"Nonce must be 12 bytes".into(),
));
}
n
}
None => {
let mut nonce_bytes = vec![0u8; 12];
SecureRandom::new()?.fill(&mut nonce_bytes)?;
nonce_bytes
}
};
self.key = Some(key);
self.nonce = Some(final_nonce);
self.is_initialized = true;
self.total_processed = 0;
Ok(())
}
#[allow(dead_code)]
pub fn encrypt_chunk(&mut self, data: &[u8]) -> Result<Vec<u8>> {
if !self.is_initialized {
return Err(CryptoError::InvalidState(
"Streaming cipher not initialized".into(),
));
}
if self.encrypt_mode.is_none() {
self.encrypt_mode = Some(true);
} else if self.encrypt_mode != Some(true) {
return Err(CryptoError::InvalidState(
"Cannot mix encryption and decryption in same session".into(),
));
}
let context = self.context.clone();
let context_ptr = context
.as_ref()
.ok_or_else(|| CryptoError::InvalidState("Context not initialized".into()))?;
let mut context_guard = context_ptr
.lock()
.map_err(|_| CryptoError::SideChannelError("Context lock poisoned".into()))?;
protect_critical_operation(&mut context_guard, || self.process_chunk(data, true))
}
#[allow(dead_code)]
pub fn decrypt_chunk(&mut self, data: &[u8]) -> Result<Vec<u8>> {
if !self.is_initialized {
return Err(CryptoError::InvalidState(
"Streaming cipher not initialized".into(),
));
}
if self.encrypt_mode.is_none() {
self.encrypt_mode = Some(false);
} else if self.encrypt_mode != Some(false) {
return Err(CryptoError::InvalidState(
"Cannot mix encryption and decryption in same session".into(),
));
}
let context = self.context.clone();
let context_ptr = context
.as_ref()
.ok_or_else(|| CryptoError::InvalidState("Context not initialized".into()))?;
let mut context_guard = context_ptr
.lock()
.map_err(|_| CryptoError::SideChannelError("Context lock poisoned".into()))?;
protect_critical_operation(&mut context_guard, || self.process_chunk(data, false))
}
#[allow(dead_code)]
pub fn finalize(&mut self) -> Result<Vec<u8>> {
if !self.is_initialized {
return Err(CryptoError::InvalidState(
"Streaming cipher not initialized".into(),
));
}
let context = self.context.clone();
let context_ptr = context
.as_ref()
.ok_or_else(|| CryptoError::InvalidState("Context not initialized".into()))?;
let mut context_guard = context_ptr
.lock()
.map_err(|_| CryptoError::SideChannelError("Context lock poisoned".into()))?;
protect_critical_operation(&mut context_guard, || {
let mut remaining = self.buffer.clone();
self.buffer.clear();
if remaining.is_empty() && !self.enable_padding {
self.is_initialized = false;
self.encrypt_mode = None;
return Ok(Vec::new());
}
let result = match self.encrypt_mode {
Some(true) => {
if self.enable_padding && !remaining.is_empty() {
let block_size = get_block_size(self.algorithm);
remaining = Pkcs7Padding::pad(&remaining, block_size)?;
}
self.process_chunk_internal(&remaining, true)?
}
Some(false) => {
if self.enable_padding {
if remaining.len() < 16 {
return Err(CryptoError::DecryptionFailed(
"Incomplete authentication tag".into(),
));
}
let decrypted = self.process_chunk_internal(&remaining, false)?;
let block_size = get_block_size(self.algorithm);
Pkcs7Padding::unpad(&decrypted, block_size)?
} else {
if remaining.len() < 16 {
return Err(CryptoError::DecryptionFailed(
"Incomplete authentication tag".into(),
));
}
self.process_chunk_internal(&remaining, false)?
}
}
None => {
return Err(CryptoError::InvalidState(
"No encryption/decryption operation performed".into(),
));
}
};
self.is_initialized = false;
self.encrypt_mode = None;
Ok(result)
})
}
#[allow(dead_code)]
pub fn reset(&mut self) -> Result<()> {
self.buffer.clear();
self.tag_buffer.clear();
self.is_initialized = false;
self.total_processed = 0;
self.nonce = None;
self.key = None;
self.encrypt_mode = None;
if let Some(ctx) = &self.context {
if let Ok(mut guard) = ctx.lock() {
guard.reset();
}
}
Ok(())
}
#[allow(dead_code)]
pub fn total_processed(&self) -> usize {
self.total_processed
}
#[allow(dead_code)]
pub fn nonce(&self) -> Option<&[u8]> {
self.nonce.as_deref()
}
#[allow(dead_code)]
pub fn set_padding_enabled(&mut self, enabled: bool) -> Result<()> {
if self.is_initialized {
return Err(CryptoError::InvalidState(
"Cannot change padding setting after initialization".to_string(),
));
}
if enabled && !self.cipher_mode.requires_padding() {
return Err(CryptoError::InvalidParameter(format!(
"Algorithm {:?} with mode {:?} does not support PKCS#7 padding",
self.algorithm, self.cipher_mode
)));
}
self.enable_padding = enabled;
Ok(())
}
#[allow(dead_code)]
pub fn is_padding_enabled(&self) -> bool {
self.enable_padding
}
#[allow(dead_code)]
fn is_key_compatible(&self, key: &Key) -> bool {
matches!(
(self.algorithm, key.algorithm()),
(Algorithm::AES128GCM, Algorithm::AES128GCM)
| (Algorithm::AES192GCM, Algorithm::AES192GCM)
| (Algorithm::AES256GCM, Algorithm::AES256GCM)
| (Algorithm::SM4GCM, Algorithm::SM4GCM)
)
}
fn process_chunk(&mut self, data: &[u8], encrypt: bool) -> Result<Vec<u8>> {
if encrypt {
self.process_chunk_encrypt(data)
} else {
self.process_chunk_decrypt(data)
}
}
fn process_chunk_encrypt(&mut self, data: &[u8]) -> Result<Vec<u8>> {
self.buffer.extend_from_slice(data);
let mut result = Vec::with_capacity(data.len() + 16);
while self.buffer.len() >= self.chunk_size {
let chunk = self.buffer[..self.chunk_size].to_vec();
self.buffer.drain(..self.chunk_size);
let processed = self.process_chunk_internal(&chunk, true)?;
result.extend_from_slice(&processed);
}
Ok(result)
}
fn process_chunk_decrypt(&mut self, data: &[u8]) -> Result<Vec<u8>> {
const TAG_SIZE: usize = 16;
self.buffer.extend_from_slice(data);
let mut result = Vec::with_capacity(data.len() + 16);
while self.buffer.len() >= self.chunk_size + TAG_SIZE {
let chunk_with_tag = self.buffer[..self.chunk_size + TAG_SIZE].to_vec();
self.buffer.drain(..self.chunk_size + TAG_SIZE);
let processed = self.process_chunk_internal(&chunk_with_tag, false)?;
result.extend_from_slice(&processed);
}
Ok(result)
}
fn process_chunk_internal(&mut self, data: &[u8], encrypt: bool) -> Result<Vec<u8>> {
let key = self
.key
.as_ref()
.ok_or_else(|| CryptoError::InvalidState("Key not initialized".into()))?;
let nonce = self
.nonce
.as_ref()
.ok_or_else(|| CryptoError::InvalidState("Nonce not initialized".into()))?;
let counter = (self.total_processed / self.chunk_size) as u32;
let mut block_nonce = nonce.clone();
let counter_bytes = counter.to_be_bytes();
block_nonce[8..12].copy_from_slice(&counter_bytes);
self.total_processed += data.len();
match self.algorithm {
Algorithm::AES128GCM | Algorithm::AES192GCM | Algorithm::AES256GCM => {
self.process_aes_gcm_chunk(key, data, &block_nonce, encrypt)
}
Algorithm::SM4GCM => self.process_sm4_gcm_chunk(key, data, &block_nonce, encrypt),
_ => Err(CryptoError::InvalidParameter(
"Unsupported algorithm for streaming".into(),
)),
}
}
fn process_aes_gcm_chunk(
&self,
key: &Key,
data: &[u8],
nonce: &[u8],
encrypt: bool,
) -> Result<Vec<u8>> {
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_128_GCM, AES_256_GCM};
let ring_alg = match self.algorithm {
Algorithm::AES128GCM => &AES_128_GCM,
Algorithm::AES256GCM => &AES_256_GCM,
_ => {
return Err(CryptoError::InvalidParameter(format!(
"Algorithm {:?} not supported by ring for streaming",
self.algorithm
)))
}
};
let secret = key.secret_bytes()?;
let unbound_key = UnboundKey::new(ring_alg, secret.as_bytes())
.map_err(|_| CryptoError::EncryptionFailed("Invalid key".into()))?;
let less_safe_key = LessSafeKey::new(unbound_key);
let nonce_array: [u8; 12] = nonce
.try_into()
.map_err(|_| CryptoError::InvalidParameter("Invalid nonce length".into()))?;
let ring_nonce = Nonce::assume_unique_for_key(nonce_array);
if encrypt {
let mut in_out = data.to_vec();
less_safe_key
.seal_in_place_append_tag(ring_nonce, Aad::from(&[]), &mut in_out)
.map_err(|_| CryptoError::EncryptionFailed("Seal failed".into()))?;
Ok(in_out)
} else {
let mut in_out = data.to_vec();
let plaintext = less_safe_key
.open_in_place(ring_nonce, Aad::from(&[]), &mut in_out)
.map_err(|_| CryptoError::DecryptionFailed("Open failed".into()))?;
Ok(plaintext.to_vec())
}
}
fn process_sm4_gcm_chunk(
&self,
key: &Key,
data: &[u8],
nonce: &[u8],
encrypt: bool,
) -> Result<Vec<u8>> {
let secret = key.secret_bytes()?;
let key_bytes: [u8; 16] = secret.as_bytes().try_into().map_err(|_| {
CryptoError::KeyError("Invalid SM4 key length, must be 128 bits".into())
})?;
use ghash::{
universal_hash::{KeyInit, UniversalHash},
GHash,
};
use sm4::cipher::{BlockEncrypt, KeyIvInit, StreamCipher};
use sm4::Sm4;
type Sm4Ctr = ctr::Ctr128BE<Sm4>;
let h = [0u8; 16];
let mut h_block = ghash::universal_hash::generic_array::GenericArray::from(h);
Sm4::new(&key_bytes.into()).encrypt_block(&mut h_block);
let h_key = h_block;
if encrypt {
let mut ghash = GHash::new(&h_key);
let mut iv = [0u8; 16];
iv[..12].copy_from_slice(nonce);
iv[15] = 2;
let mut ciphertext = data.to_vec();
let mut cipher = Sm4Ctr::new(&key_bytes.into(), &iv.into());
cipher.apply_keystream(&mut ciphertext);
ghash.update_padded(&ciphertext);
let mut len_block = [0u8; 16];
let ct_len = (ciphertext.len() as u64) * 8;
len_block[8..].copy_from_slice(&ct_len.to_be_bytes());
ghash.update_padded(&len_block);
let mut tag = ghash.finalize();
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
let mut tag_mask = [0u8; 16];
let mut mask_cipher = Sm4Ctr::new(&key_bytes.into(), &j0.into());
mask_cipher.apply_keystream(&mut tag_mask);
for i in 0..16 {
tag[i] ^= tag_mask[i];
}
let mut result = ciphertext;
result.extend_from_slice(&tag);
Ok(result)
} else {
if data.len() < 16 {
return Err(CryptoError::DecryptionFailed(
"Chunk too short for tag".into(),
));
}
let (ciphertext, received_tag) = data.split_at(data.len() - 16);
let mut ghash = GHash::new(&h_key);
ghash.update_padded(ciphertext);
let mut len_block = [0u8; 16];
let ct_len = (ciphertext.len() as u64) * 8;
len_block[8..].copy_from_slice(&ct_len.to_be_bytes());
ghash.update_padded(&len_block);
let mut tag = ghash.finalize();
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
let mut tag_mask = [0u8; 16];
let mut mask_cipher = Sm4Ctr::new(&key_bytes.into(), &j0.into());
mask_cipher.apply_keystream(&mut tag_mask);
for i in 0..16 {
tag[i] ^= tag_mask[i];
}
use subtle::ConstantTimeEq;
if tag.as_slice().ct_eq(received_tag).unwrap_u8() != 1 {
return Err(CryptoError::DecryptionFailed("Chunk tag mismatch".into()));
}
let mut iv = [0u8; 16];
iv[..12].copy_from_slice(nonce);
iv[15] = 2;
let mut plaintext = ciphertext.to_vec();
let mut cipher = Sm4Ctr::new(&key_bytes.into(), &iv.into());
cipher.apply_keystream(&mut plaintext);
Ok(plaintext)
}
}
}
#[allow(dead_code)]
pub struct StreamingCipherBuilder {
algorithm: Algorithm,
chunk_size: usize,
side_channel_config: Option<SideChannelConfig>,
}
impl StreamingCipherBuilder {
#[allow(dead_code)]
pub fn new(algorithm: Algorithm) -> Self {
Self {
algorithm,
chunk_size: 4096, side_channel_config: None,
}
}
#[allow(dead_code)]
pub fn chunk_size(mut self, size: usize) -> Self {
self.chunk_size = size;
self
}
#[allow(dead_code)]
pub fn side_channel_config(mut self, config: SideChannelConfig) -> Self {
self.side_channel_config = Some(config);
self
}
#[allow(dead_code)]
pub fn build(self) -> Result<StreamingCipher> {
match self.side_channel_config {
Some(config) => {
StreamingCipher::with_side_channel_config(self.algorithm, self.chunk_size, config)
}
None => StreamingCipher::new(self.algorithm, self.chunk_size),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::key::Key;
#[test]
fn test_streaming_cipher_creation() {
let cipher = StreamingCipher::new(Algorithm::AES256GCM, 1024).unwrap();
assert_eq!(cipher.total_processed(), 0);
assert!(!cipher.is_initialized);
}
#[test]
fn test_streaming_cipher_builder() {
let cipher = StreamingCipherBuilder::new(Algorithm::AES256GCM)
.chunk_size(2048)
.build()
.unwrap();
assert_eq!(cipher.chunk_size, 2048);
}
#[test]
fn test_streaming_encryption_decryption() {
let mut encryptor = StreamingCipher::new(Algorithm::AES256GCM, 64).unwrap(); let mut decryptor = StreamingCipher::new(Algorithm::AES256GCM, 64).unwrap();
let key = Key::new_active(Algorithm::AES256GCM, vec![0u8; 32]).unwrap();
encryptor.initialize(key.clone(), None).unwrap();
decryptor
.initialize(key.clone(), encryptor.nonce().map(|n| n.to_vec()))
.unwrap();
let test_data = b"Hello, streaming encryption world! This is a test message that is longer than the chunk size.";
let mut encrypted = Vec::with_capacity(test_data.len() + 32);
encrypted.extend_from_slice(&encryptor.encrypt_chunk(test_data).unwrap());
let final_encrypted = encryptor.finalize().unwrap();
encrypted.extend_from_slice(&final_encrypted);
assert!(!encrypted.is_empty());
let mut decrypted = Vec::with_capacity(encrypted.len());
decrypted.extend_from_slice(&decryptor.decrypt_chunk(&encrypted).unwrap());
let final_decrypted = decryptor.finalize().unwrap();
decrypted.extend_from_slice(&final_decrypted);
assert_eq!(decrypted, test_data);
}
#[test]
fn test_streaming_large_data() {
let mut encryptor = StreamingCipher::new(Algorithm::AES256GCM, 512).unwrap();
let mut decryptor = StreamingCipher::new(Algorithm::AES256GCM, 512).unwrap();
let key = Key::new_active(Algorithm::AES256GCM, vec![0u8; 32]).unwrap();
encryptor.initialize(key.clone(), None).unwrap();
decryptor
.initialize(key.clone(), encryptor.nonce().map(|n| n.to_vec()))
.unwrap();
let large_data = vec![0x42u8; 2048];
let mut all_encrypted = Vec::with_capacity(2048);
for chunk in large_data.chunks(256) {
let encrypted_chunk = encryptor.encrypt_chunk(chunk).unwrap();
all_encrypted.extend_from_slice(&encrypted_chunk);
}
let final_encrypted = encryptor.finalize().unwrap();
all_encrypted.extend_from_slice(&final_encrypted);
let mut all_decrypted = Vec::with_capacity(2048);
for chunk in all_encrypted.chunks(256 + 16) {
let decrypted_chunk = decryptor.decrypt_chunk(chunk).unwrap();
all_decrypted.extend_from_slice(&decrypted_chunk);
}
let final_decrypted = decryptor.finalize().unwrap();
all_decrypted.extend_from_slice(&final_decrypted);
assert_eq!(all_decrypted.len(), large_data.len());
assert_eq!(&all_decrypted[..large_data.len()], &large_data[..]);
}
#[test]
fn test_sm4_streaming_encryption_decryption() {
let mut encryptor = StreamingCipher::new(Algorithm::SM4GCM, 64).unwrap();
let mut decryptor = StreamingCipher::new(Algorithm::SM4GCM, 64).unwrap();
let key = Key::new_active(Algorithm::SM4GCM, vec![0x01u8; 16]).unwrap();
encryptor.initialize(key.clone(), None).unwrap();
decryptor
.initialize(key.clone(), encryptor.nonce().map(|n| n.to_vec()))
.unwrap();
let test_data =
b"SM4 streaming test message. It should work correctly across multiple chunks.";
let mut encrypted = Vec::with_capacity(test_data.len() + 32);
encrypted.extend_from_slice(&encryptor.encrypt_chunk(test_data).unwrap());
encrypted.extend_from_slice(&encryptor.finalize().unwrap());
let mut decrypted = Vec::with_capacity(encrypted.len());
decrypted.extend_from_slice(&decryptor.decrypt_chunk(&encrypted).unwrap());
decrypted.extend_from_slice(&decryptor.finalize().unwrap());
assert_eq!(decrypted, test_data);
}
#[test]
fn test_pkcs7_padding_integration() {
let mut encryptor = StreamingCipher::new(Algorithm::AES256GCM, 64).unwrap();
let mut decryptor = StreamingCipher::new(Algorithm::AES256GCM, 64).unwrap();
let key = Key::new_active(Algorithm::AES256GCM, vec![0u8; 32]).unwrap();
assert!(!encryptor.is_padding_enabled());
assert!(!decryptor.is_padding_enabled());
encryptor.initialize(key.clone(), None).unwrap();
decryptor
.initialize(key.clone(), encryptor.nonce().map(|n| n.to_vec()))
.unwrap();
let test_data = b"Hello, this is a test message with irregular length!";
let mut encrypted = Vec::with_capacity(test_data.len() + 32);
encrypted.extend_from_slice(&encryptor.encrypt_chunk(test_data).unwrap());
encrypted.extend_from_slice(&encryptor.finalize().unwrap());
let mut decrypted = Vec::with_capacity(encrypted.len());
decrypted.extend_from_slice(&decryptor.decrypt_chunk(&encrypted).unwrap());
decrypted.extend_from_slice(&decryptor.finalize().unwrap());
assert_eq!(decrypted, test_data);
}
#[test]
fn test_padding_configuration() {
let mut cipher = StreamingCipher::new(Algorithm::AES256GCM, 1024).unwrap();
assert!(cipher.set_padding_enabled(true).is_err());
assert!(!cipher.is_padding_enabled());
let key = Key::new_active(Algorithm::AES256GCM, vec![0u8; 32]).unwrap();
cipher.initialize(key, None).unwrap();
assert!(cipher.set_padding_enabled(false).is_err());
}
}