diff --git a/Cargo.lock b/Cargo.lock index 8009b7d..49dfc69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -621,6 +621,7 @@ dependencies = [ name = "secured-cipher" version = "0.3.1" dependencies = [ + "rayon", "secured-cipher-key", ] diff --git a/Cargo.toml b/Cargo.toml index 6959fd9..66a3b9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,6 @@ version = "^7.3.1" [[bin]] name = "secured" path = "src/main.rs" + +[profile.bench] +debug = true diff --git a/benches/Cargo.toml b/benches/Cargo.toml index aacc25b..9960f6a 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -16,3 +16,6 @@ secured-cipher = { path = "../cipher/" } name = "chacha20" path = "src/chacha20.rs" harness = false + +[profile.bench] +debug = true diff --git a/benches/src/chacha20.rs b/benches/src/chacha20.rs index c26ac78..014d053 100644 --- a/benches/src/chacha20.rs +++ b/benches/src/chacha20.rs @@ -1,7 +1,10 @@ use criterion::{ criterion_group, criterion_main, BenchmarkId, Criterion, PlotConfiguration, Throughput, }; -use secured_cipher::{permutation::chacha20::CHACHA20_NONCE_SIZE, Cipher, CipherMode}; +use secured_cipher::{ + algorithm::chacha20::CHACHA20_NONCE_SIZE, AlgorithmKeyIVInit, AlgorithmProcess, + AlgorithmProcessInPlace, ChaCha20, +}; const KB: usize = 1024; const MB: usize = 1024 * KB; @@ -11,18 +14,45 @@ fn bench(c: &mut Criterion) { let plot_config = PlotConfiguration::default().summary_scale(criterion::AxisScale::Logarithmic); group.plot_config(plot_config); - for size in &[KB, 2 * KB, 4 * KB, 8 * KB, 16 * KB, 32 * KB, 64 * KB, MB] { + for size in &[ + KB, + 2 * KB, + 4 * KB, + 8 * KB, + MB, + 2 * MB, + 4 * MB, + 8 * MB, + 100 * MB, + 200 * MB, + 400 * MB, + 800 * MB, + ] { let key = [0u8; 32]; let iv = [1u8; CHACHA20_NONCE_SIZE]; group.throughput(Throughput::Bytes(*size as u64)); - let mut cipher = Cipher::new(CipherMode::ChaCha20); - cipher.init(&key, &iv); - - group.bench_with_input(BenchmarkId::new("process", size), size, |b, &_size| { - b.iter(|| cipher.encrypt(&vec![0u8; *size])); - }); + let mut chacha20 = ChaCha20::new(); + chacha20.init(&key, &iv); + + group.bench_with_input( + BenchmarkId::new("process", size), + size, + |b, &_size| { + let mut bytes = vec![0u8; *size]; + b.iter(|| chacha20.process_in_place(&mut bytes)); + }, + ); + + group.bench_with_input( + BenchmarkId::new("process_in_place", size), + size, + |b, &_size| { + let mut bytes = vec![0u8; *size]; + b.iter(|| chacha20.process(&mut bytes)); + }, + ); } group.finish(); diff --git a/cipher/Cargo.toml b/cipher/Cargo.toml index 36a2011..fede627 100644 --- a/cipher/Cargo.toml +++ b/cipher/Cargo.toml @@ -11,3 +11,6 @@ description = "Pure Rust implementation of the ChaCha cipher family" package = "secured-cipher-key" path = "./key" version = "0.2.0" + +[dependencies.rayon] +version = "1.8.0" diff --git a/cipher/src/permutation/chacha20/core.rs b/cipher/src/algorithm/chacha20/core.rs similarity index 99% rename from cipher/src/permutation/chacha20/core.rs rename to cipher/src/algorithm/chacha20/core.rs index 5393c83..36998e7 100644 --- a/cipher/src/permutation/chacha20/core.rs +++ b/cipher/src/algorithm/chacha20/core.rs @@ -62,7 +62,7 @@ pub fn quarter_round(a: usize, b: usize, c: usize, d: usize, state: &mut Block) /// Runs the ChaCha20 permutation on the provided state. pub fn permute(state: &Block) -> Block { - let mut block = state.clone(); + let mut block = *state; // The ChaCha20 permutation consists of 20 rounds of quarter round operations. run_rounds(&mut block); diff --git a/cipher/src/permutation/chacha20/mod.rs b/cipher/src/algorithm/chacha20/mod.rs similarity index 76% rename from cipher/src/permutation/chacha20/mod.rs rename to cipher/src/algorithm/chacha20/mod.rs index 2705b94..2615293 100644 --- a/cipher/src/permutation/chacha20/mod.rs +++ b/cipher/src/algorithm/chacha20/mod.rs @@ -1,9 +1,14 @@ mod core; pub use core::{permute, xor_bytes, Block, CHACHA20_NONCE_SIZE, CONSTANTS, STATE_WORDS}; -use super::Permutation; +use crate::EncryptionAlgorithm; + +use super::{AlgorithmKeyIVInit, AlgorithmProcess, AlgorithmProcessInPlace}; + +use rayon::prelude::*; /// The `ChaCha20` struct represents the ChaCha20 stream cipher. +#[derive(Clone)] pub struct ChaCha20 { state: Block, } @@ -36,7 +41,7 @@ impl ChaCha20 { /// /// # Example /// ``` - /// use secured_cipher::{ChaCha20, Permutation}; + /// use secured_cipher::{ChaCha20, AlgorithmKeyIVInit}; /// /// let mut chacha20 = ChaCha20::new(); /// chacha20.init(&[0_u8; 32], &[0_u8; 12]); @@ -70,9 +75,29 @@ impl ChaCha20 { // Return the generated 64-byte keystream block keystream } + + pub fn keystream_at(&self, counter: u32) -> [u8; 64] { + // Initialize an array to hold the keystream + let mut keystream = [0u8; 64]; + + // Set the block counter to the provided value + let mut state = self.state; + state[12] = counter; + + // Perform the ChaCha20 permutation on the current state + let block = permute(&state); + + // Convert the 32-bit words from the permuted block into bytes and copy them into the keystream + for (bytes, word) in keystream.chunks_exact_mut(4).zip(block) { + bytes.copy_from_slice(&word.to_le_bytes()); + } + + // Return the generated 64-byte keystream block + keystream + } } -impl Permutation for ChaCha20 { +impl AlgorithmKeyIVInit for ChaCha20 { /// Initializes the ChaCha20 cipher with a given key and IV (initialization vector). /// /// This method sets up the cipher's internal state which includes the ChaCha20 constants, the provided key, @@ -108,7 +133,9 @@ impl Permutation for ChaCha20 { *val = u32::from_le_bytes(chunk.try_into().unwrap()); } } +} +impl AlgorithmProcess for ChaCha20 { /// Processes input data using the ChaCha20 cipher algorithm. /// /// This function applies the ChaCha20 encryption or decryption process to the given input bytes. @@ -123,18 +150,9 @@ impl Permutation for ChaCha20 { /// # Returns /// A `Vec` containing the processed data (encrypted or decrypted). /// - /// # Behavior - /// The function divides the input data into 64-byte blocks. For each block, it generates a unique - /// keystream using the `next_keystream` method. Each block of the input data is then XORed with its - /// corresponding keystream block. This method ensures that each block is encrypted or decrypted - /// with a different keystream, which is essential for the security of the cipher. - /// - /// After processing all blocks, the function clears the internal state to prevent any residual - /// sensitive data from remaining in memory. - /// /// # Example /// ``` - /// use secured_cipher::{ChaCha20, Permutation}; + /// use secured_cipher::{ChaCha20, algorithm::{AlgorithmKeyIVInit, AlgorithmProcess}}; /// /// let mut chacha20 = ChaCha20::new(); /// chacha20.init(&[0_u8; 32], &[0_u8; 12]); @@ -152,27 +170,50 @@ impl Permutation for ChaCha20 { let mut out = bytes_in.to_owned(); // Process each 64-byte block of the input data - out.chunks_mut(64).for_each(|plain_chunk| { - // Generate the keystream for the current block - let keystream = self.next_keystream(); - // XOR the block with the keystream to perform encryption/decryption - xor_bytes(plain_chunk, &keystream); - }); - - // Clear the internal state after processing to maintain security - self.clear(); + out + .par_chunks_mut(64 * 100) + .enumerate() + .for_each(|(i, par_chunk)| { + par_chunk.chunks_mut(64).enumerate().for_each(|(j, chunk)| { + // Generate the keystream for the current block + let keystream = self.keystream_at(((i * 100) + j + 1) as u32); + // XOR the block with the keystream to perform encryption/decryption + xor_bytes(chunk, &keystream); + }); + }); // Return the processed data out.to_vec() } +} - /// Clears the internal counter of the cipher. - fn clear(&mut self) { - // Reset the block counter - self.state[12] = 1; +impl AlgorithmProcessInPlace for ChaCha20 { + /// Processes input data using the ChaCha20 cipher algorithm. + /// + /// This function applies the ChaCha20 encryption or decryption process to the given input bytes. + /// + /// # Arguments + /// * `bytes` - A slice of bytes representing the input data to be processed (either plaintext for encryption + /// or ciphertext for decryption). + fn process_in_place(&self, bytes: &mut [u8]) { + // Process 6.4 kilobytes per thread + bytes + .par_chunks_mut(64 * 100) + .enumerate() + .for_each(|(i, par_chunk)| { + // Process each 64-byte block of the input data + par_chunk.chunks_mut(64).enumerate().for_each(|(j, chunk)| { + // Generate the keystream for the current block + let keystream = self.keystream_at(((i * 100) + j + 1) as u32); + // XOR the block with the keystream to perform encryption/decryption + xor_bytes(chunk, &keystream); + }); + }); } } +impl EncryptionAlgorithm for ChaCha20 {} + #[cfg(test)] mod tests { use super::*; @@ -277,4 +318,19 @@ mod tests { assert_eq!(decrypted_data, data); } + + #[test] + fn it_process_in_place_as_expected() { + let mut chacha20_1 = ChaCha20::new(); + chacha20_1.init(&[0u8; 32], &[0u8; CHACHA20_NONCE_SIZE]); + let mut chacha20_2 = ChaCha20::new(); + chacha20_2.init(&[0u8; 32], &[0u8; CHACHA20_NONCE_SIZE]); + let mut data = [0u8; 64 * 1000]; + let data2 = data.clone(); + + chacha20_1.process_in_place(&mut data); + let encrypted_sync = chacha20_2.process(&data2); + + assert_eq!(data.to_vec(), encrypted_sync); + } } diff --git a/cipher/src/permutation/mod.rs b/cipher/src/algorithm/mod.rs similarity index 50% rename from cipher/src/permutation/mod.rs rename to cipher/src/algorithm/mod.rs index f225626..33ab9fc 100644 --- a/cipher/src/permutation/mod.rs +++ b/cipher/src/algorithm/mod.rs @@ -10,12 +10,8 @@ pub use chacha20::ChaCha20; /// Re-exporting `Poly1305` for direct use. pub use poly1305::{Poly1305, SignedEnvelope}; -/// `Permutation` trait defines the common operations for permutation-based cryptographic algorithms. -/// -/// This trait provides the fundamental methods required for encryption and decryption processes -/// in ciphers like ChaCha20 and XChaCha20. -pub trait Permutation { - /// Initializes the permutation with a key and an initialization vector (IV). +pub trait AlgorithmKeyIVInit { + /// Initializes the algorithm with a key and an initialization vector (IV). /// /// This method sets up the internal state of the cipher using the provided key and IV, /// preparing it for either encryption or decryption. @@ -24,7 +20,20 @@ pub trait Permutation { /// * `key` - A byte slice representing the cryptographic key. /// * `iv` - A byte slice representing the initialization vector. fn init(&mut self, key: &[u8], iv: &[u8]); +} + +pub trait AlgorithmKeyInit { + /// Initializes the algorithm with a key. + /// + /// This method sets up the internal state of the cipher using the provided key, + /// preparing it for either encryption or decryption. + /// + /// # Arguments + /// * `key` - A byte slice representing the cryptographic key. + fn init(&mut self, key: &[u8]); +} +pub trait AlgorithmProcess { /// Processes the provided data (either encrypts or decrypts, depending on the implementation). /// /// This method applies the cipher's permutation logic to the provided data, returning the @@ -36,10 +45,22 @@ pub trait Permutation { /// # Returns /// A vector of bytes representing the processed data. fn process(&mut self, data: &[u8]) -> Vec; +} - /// Clears the internal state of the cipher. +pub trait AlgorithmProcessInPlace { + /// Processes the provided data (either encrypts or decrypts, depending on the implementation). /// - /// This method is used to reset the cipher's state, ensuring that no sensitive information - /// is left in memory after the cryptographic operations are complete. - fn clear(&mut self); + /// This method applies the cipher's permutation logic to the provided data, returning the + /// processed data as a new vector of bytes. + /// + /// # Arguments + /// * `data` - A byte slice of data to be processed (encrypted or decrypted). + /// + /// # Returns + /// A vector of bytes representing the processed data. + fn process_in_place(&self, data: &mut [u8]); } + +pub trait EncryptionAlgorithm: AlgorithmKeyIVInit + AlgorithmProcess {} + +pub trait AEADAlgorithm: AlgorithmKeyInit + AlgorithmProcess {} diff --git a/cipher/src/permutation/poly1305/core.rs b/cipher/src/algorithm/poly1305/core.rs similarity index 98% rename from cipher/src/permutation/poly1305/core.rs rename to cipher/src/algorithm/poly1305/core.rs index 0c476b3..77bd8a5 100644 --- a/cipher/src/permutation/poly1305/core.rs +++ b/cipher/src/algorithm/poly1305/core.rs @@ -130,7 +130,7 @@ pub fn apply_poly1305_mod_p( hash[4] = (*d4 as u32) & 0x3ff_ffff; hash[0] += c * 5; - c = (hash[0] >> 26) as u32; + c = hash[0] >> 26; hash[0] &= 0x3ff_ffff; hash[1] += c; } @@ -181,7 +181,7 @@ pub fn finalize_poly1305_hash(hash: &mut [u32; 5]) { let mut g4 = hash[4].wrapping_add(c).wrapping_sub(1 << 26); - let mut mask = (g4 >> 31 - 1).wrapping_sub(1); + let mut mask = (g4 >> (31 - 1)).wrapping_sub(1); g0 &= mask; g1 &= mask; g2 &= mask; diff --git a/cipher/src/permutation/poly1305/mod.rs b/cipher/src/algorithm/poly1305/mod.rs similarity index 90% rename from cipher/src/permutation/poly1305/mod.rs rename to cipher/src/algorithm/poly1305/mod.rs index 6ac6514..d8c5617 100644 --- a/cipher/src/permutation/poly1305/mod.rs +++ b/cipher/src/algorithm/poly1305/mod.rs @@ -4,9 +4,12 @@ pub use core::{ calculate_poly1305_h_values, finalize_poly1305_hash, poly1305_hash_to_tag, }; -use super::Permutation; +use crate::AEADAlgorithm; + +use super::{AlgorithmKeyInit, AlgorithmProcess}; /// Define the Poly1305 struct for the Poly1305 MAC algorithm. +#[derive(Default)] pub struct Poly1305 { // r: 5-element array storing part of the key. r: [u32; 5], @@ -78,7 +81,7 @@ impl Poly1305 { } } -impl Permutation for Poly1305 { +impl AlgorithmKeyInit for Poly1305 { /// Initializes the Poly1305 state with the given key. /// /// This method sets up the Poly1305 state using a 32-byte key. The key is split @@ -94,7 +97,7 @@ impl Permutation for Poly1305 { /// /// # Notes /// The Initialization Vector (`_iv`) is not used in Poly1305 and can be passed as an empty slice. - fn init(&mut self, key: &[u8], _iv: &[u8]) { + fn init(&mut self, key: &[u8]) { self.r[0] = u32::from_le_bytes([key[0], key[1], key[2], key[3]]) & 0x3ff_ffff; self.r[1] = u32::from_le_bytes([key[3], key[4], key[5], key[6]]) & 0x3ff_ff03; self.r[2] = u32::from_le_bytes([key[6], key[7], key[8], key[9]]) & 0x3ff_c0ff; @@ -105,7 +108,9 @@ impl Permutation for Poly1305 { self.pad[2] = u32::from_le_bytes([key[24], key[25], key[26], key[27]]); self.pad[3] = u32::from_le_bytes([key[28], key[29], key[30], key[31]]); } +} +impl AlgorithmProcess for Poly1305 { /// Processes the given data and computes the MAC. /// /// This method processes the input data in 16-byte blocks to compute the @@ -118,10 +123,10 @@ impl Permutation for Poly1305 { /// # Returns /// A vector of bytes (`Vec`) containing the computed MAC. fn process(&mut self, data: &[u8]) -> Vec { - let mut blocks = data.chunks_exact(16); + let blocks = data.chunks_exact(16); let partial = blocks.remainder(); - while let Some(block) = blocks.next() { + for block in blocks { self.compute_block(block.try_into().unwrap(), false); } @@ -135,28 +140,9 @@ impl Permutation for Poly1305 { self.finalize().to_vec() } - - /// Clears the internal state of the Poly1305 instance. - /// - /// This method resets the internal state variables (`r`, `h`, and `pad`) to zero. - /// It is useful for security purposes when the MAC computation is complete and - /// the instance needs to be cleared before being reused or discarded. - fn clear(&mut self) { - self.r = [0u32; 5]; - self.h = [0u32; 5]; - self.pad = [0u32; 4]; - } } -impl Default for Poly1305 { - fn default() -> Self { - Self { - r: [0u32; 5], - h: [0u32; 5], - pad: [0u32; 4], - } - } -} +impl AEADAlgorithm for Poly1305 {} /// SignedEnvelope struct for handling data with its associated MAC. pub struct SignedEnvelope { diff --git a/cipher/src/lib.rs b/cipher/src/lib.rs index 06ea034..8e27944 100644 --- a/cipher/src/lib.rs +++ b/cipher/src/lib.rs @@ -56,47 +56,47 @@ //! - `core`: Core functionalities and algorithmic implementations. //! - `stream`: Internal stream cipher operations, including `ChaChaStream`. -pub mod permutation; +pub mod algorithm; pub use secured_cipher_key::{random_bytes, Key, KeyDerivationStrategy}; -pub use permutation::{ChaCha20, Permutation, Poly1305, SignedEnvelope}; +pub use algorithm::{ + AEADAlgorithm, AlgorithmKeyIVInit, AlgorithmKeyInit, AlgorithmProcess, AlgorithmProcessInPlace, + ChaCha20, EncryptionAlgorithm, Poly1305, SignedEnvelope, +}; use std::error::Error; /// The `Cipher` struct provides a common interface for cryptographic operations, /// specifically focusing on encryption and decryption. pub struct Cipher { - /// The cipher's internal permutation logic. - permutation: Box, + /// The cipher's internal encryption logic. + encryption: Box, // The cipher's AEAD (authenticated encryption with associated data) logic. - aead: Poly1305, + aead: Box, } pub enum CipherMode { - ChaCha20, - // TODO: XChaCha20, + ChaCha20Poly1305, + Custom(Box, Box), } impl Cipher { /// Constructs a new `Cipher` instance using the specified cipher mode. /// /// # Arguments - /// * `mode` - The mode of cipher (ChaCha20 or XChaCha20) to use. + /// * `mode` - The mode of cipher (ChaCha20 or Custom) to use. /// /// # Returns /// A new instance of `Cipher`. pub fn new(mode: CipherMode) -> Self { - let permutation: Box = match mode { - _ => Box::new(ChaCha20::new()), - // TODO: CipherMode::XChaCha20 => Box::new(XChaCha20::new()), + let (encryption, aead): (Box, Box) = match mode { + CipherMode::ChaCha20Poly1305 => (Box::new(ChaCha20::new()), Box::new(Poly1305::new())), + CipherMode::Custom(encryption, aead) => (encryption, aead), }; - Self { - permutation, - aead: Poly1305::new(), - } + Self { encryption, aead } } /// Initializes the cipher with a key and IV (initialization vector). @@ -109,11 +109,11 @@ impl Cipher { /// # Returns /// A mutable reference to the cipher instance. pub fn init(&mut self, key: &[u8], iv: &[u8]) -> &mut Self { - self.permutation.init(key, iv); + self.encryption.init(key, iv); // The Poly1305 authenticator uses a subkey derived from the cipher's key. // This subkey is generated by running the ChaCha20 permutation on a block of zeros. - self.aead.init(&self.permutation.process(&[0; 64]), iv); + self.aead.init(&self.encryption.process(&[0; 64])); self } @@ -127,7 +127,7 @@ impl Cipher { /// Encrypted data as a vector of bytes (`Bytes`). pub fn encrypt(&mut self, data: &[u8]) -> Vec { // Encrypt the data using the ChaCha20 permutation - self.permutation.process(data) + self.encryption.process(data) } /// Signs the provided data. @@ -161,7 +161,7 @@ impl Cipher { /// Decrypted data as a vector of bytes (`Bytes`). pub fn decrypt(&mut self, data: &[u8]) -> Vec { // Decrypt the data using the ChaCha20 permutation - self.permutation.process(&data) + self.encryption.process(data) } /// Decrypts the provided data and verifies the MAC. @@ -182,7 +182,7 @@ impl Cipher { } // Decrypt the data using the ChaCha20 permutation - Ok(self.permutation.process(&envelope.data)) + Ok(self.encryption.process(&envelope.data)) } } @@ -192,7 +192,7 @@ impl Default for Cipher { /// # Returns /// A new instance of `Cipher` with XChaCha20 mode. fn default() -> Self { - Self::new(CipherMode::ChaCha20) + Self::new(CipherMode::ChaCha20Poly1305) } } diff --git a/enclave/src/lib.rs b/enclave/src/lib.rs index bb6afdb..d520abb 100644 --- a/enclave/src/lib.rs +++ b/enclave/src/lib.rs @@ -5,7 +5,7 @@ pub use errors::EnclaveError; pub use traits::{Decryptable, Encryptable}; pub use secured_cipher::{ - permutation::chacha20::CHACHA20_NONCE_SIZE, random_bytes, Cipher, Key, KeyDerivationStrategy, + algorithm::chacha20::CHACHA20_NONCE_SIZE, random_bytes, Cipher, Key, KeyDerivationStrategy, SignedEnvelope, }; diff --git a/src/main.rs b/src/main.rs index 322ee48..e955150 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,8 +100,8 @@ fn decrypt_file(password: &String, filename: &String) { /// # Returns /// A `Vec` containing the contents of the file. fn get_file_as_byte_vec(filename: &String) -> Vec { - let mut f = File::open(&filename).expect("no file found"); - let metadata = metadata(&filename).expect("unable to read metadata"); + let mut f = File::open(filename).expect("no file found"); + let metadata = metadata(filename).expect("unable to read metadata"); let mut buffer = vec![0; metadata.len() as usize]; f.read(&mut buffer).expect("buffer overflow");