diff --git a/Cargo.lock b/Cargo.lock index 9bdff53..f63c55a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -621,7 +621,6 @@ dependencies = [ name = "secured-cipher" version = "0.1.0" dependencies = [ - "rayon", "secured-cipher-key", ] diff --git a/benches/src/chacha20.rs b/benches/src/chacha20.rs index 27f7444..7cf2402 100644 --- a/benches/src/chacha20.rs +++ b/benches/src/chacha20.rs @@ -1,14 +1,17 @@ use criterion::{ criterion_group, criterion_main, BenchmarkId, Criterion, PlotConfiguration, Throughput, }; -use secured_cipher::chacha20::{ChaChaStream, KEY_SIZE, NONCE_SIZE}; +use secured_cipher::{ + permutation::core::{CHACHA20_NONCE_SIZE, KEY_SIZE}, + Cipher, CipherMode, +}; const KB: usize = 1024; const MB: usize = 1024 * KB; const GB: usize = 1024 * MB; fn bench(c: &mut Criterion) { - let mut group = c.benchmark_group("ChaChaStream"); + let mut group = c.benchmark_group("ChaCha20"); let plot_config = PlotConfiguration::default().summary_scale(criterion::AxisScale::Logarithmic); group.plot_config(plot_config); @@ -36,17 +39,15 @@ fn bench(c: &mut Criterion) { GB, ] { let key = [0u8; KEY_SIZE]; - let iv = [1u8; NONCE_SIZE]; + let iv = [1u8; CHACHA20_NONCE_SIZE]; group.throughput(Throughput::Bytes(*size as u64)); - // group.bench_with_input(BenchmarkId::new("new", size), size, |b, &_size| { - // b.iter(|| ChaChaStream::new(key, iv)); - // }); + let mut cipher = Cipher::new(CipherMode::ChaCha20); + cipher.init(&key, &iv); - let mut stream = ChaChaStream::new(key, iv); group.bench_with_input(BenchmarkId::new("process", size), size, |b, &_size| { - b.iter(|| stream.process(&vec![0u8; *size])); + b.iter(|| cipher.encrypt(&vec![0u8; *size])); }); } diff --git a/cipher/Cargo.toml b/cipher/Cargo.toml index b58c8f0..22be476 100644 --- a/cipher/Cargo.toml +++ b/cipher/Cargo.toml @@ -10,7 +10,3 @@ description = "Pure Rust implementation of the ChaCha cipher family" package = "secured-cipher-key" path = "./key" version = "0.1.0" - -[dependencies.rayon] -package = "rayon" -version = "~1.8.0" diff --git a/cipher/src/chacha20/core.rs b/cipher/src/chacha20/core.rs deleted file mode 100644 index be5190b..0000000 --- a/cipher/src/chacha20/core.rs +++ /dev/null @@ -1,190 +0,0 @@ -/// Constants for the ChaCha20 algorithm. -/// These four 32-bit words represent the ASCII encoding of "expand 32-byte k", -/// used in the state initialization of the ChaCha20 block. -pub const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]; - -/// Number of 32-bit words in the ChaCha state. -/// The ChaCha20 state consists of 16 words, each of which is 32 bits long. -pub const STATE_WORDS: usize = 16; - -/// Number of ChaCha20 rounds. -/// This constant defines how many rounds of the main ChaCha20 algorithm will be executed. -/// The standard number of rounds is 20. -pub const ROUNDS: usize = 20; - -/// Size of the nonce in bytes. -/// The nonce is a 64-bit (8 bytes) value used to make each block unique. -pub const NONCE_SIZE: usize = 8; - -/// Size of the key in bytes. -/// The key is a 256-bit (32 bytes) value used for encryption and decryption. -pub const KEY_SIZE: usize = 32; - -/// Performs the quarter round operation on the state. -/// -/// This operation modifies four words in the state as per the ChaCha20 algorithm's quarter round rules. -/// It involves a series of addition, XOR, and rotation operations to mix the input words. -/// -/// # Arguments -/// * `a`, `b`, `c`, `d` - Indices of the state words to be modified. -/// * `state` - A mutable reference to the 512-bit state array. -pub fn quarter_round(a: usize, b: usize, c: usize, d: usize, state: &mut [u32; STATE_WORDS]) { - state[a] = state[a].wrapping_add(state[b]); - state[d] ^= state[a]; - state[d] = state[d].rotate_left(16); - - state[c] = state[c].wrapping_add(state[d]); - state[b] ^= state[c]; - state[b] = state[b].rotate_left(12); - - state[a] = state[a].wrapping_add(state[b]); - state[d] ^= state[a]; - state[d] = state[d].rotate_left(8); - - state[c] = state[c].wrapping_add(state[d]); - state[b] ^= state[c]; - state[b] = state[b].rotate_left(7); -} - -/// Performs the ChaCha20 rounds on the provided state. -/// -/// Applies the quarter round operation multiple times as defined by the ROUNDS constant. -/// Optionally, if an additional state is provided, it adds this state to the output after the rounds. -/// -/// # Arguments -/// * `out` - A mutable reference to the state array on which the rounds are performed. -/// * `add` - An optional additional state array to add to `out` after the rounds are completed. -pub fn chacha20_rounds(out: &mut [u32; 16], add: Option<[u32; 16]>) { - for _ in 0..ROUNDS { - // Odd rounds - quarter_round(0, 4, 8, 12, out); - quarter_round(1, 5, 9, 13, out); - quarter_round(2, 6, 10, 14, out); - quarter_round(3, 7, 11, 15, out); - // Even rounds - quarter_round(0, 5, 10, 15, out); - quarter_round(1, 6, 11, 12, out); - quarter_round(2, 7, 8, 13, out); - quarter_round(3, 4, 9, 14, out); - } - - if let Some(add) = add { - // The original ChaCha20 algorithm adds the original state to the output of the rounds. - for i in 0..16 { - out[i] = out[i].wrapping_add(add[i]); - } - } -} - -pub fn seek_keystream(state: &[u32; 16], n: u64) -> [u32; 16] { - let mut state = state.clone(); - safe_2words_counter_increment_n(&mut state[12..14], n); - - let mut keystream = state; - chacha20_rounds(&mut keystream, Some(state)); - - keystream -} - -/// XORs two 512-bit state arrays. -/// This function modifies the first array in place. -/// -/// # Arguments -/// * `a` - A mutable reference to the first state array. -/// * `b` - A reference to the second state array. -/// -/// # Panics -/// Panics if the two arrays are not of equal length. -pub fn xor(left: &mut [u32], right: &[u32]) { - assert!( - right.len() >= left.len(), - "The left array can't be XORed completely with the right array" - ); - left - .iter_mut() - .zip(right.iter()) - .for_each(|(left, right)| *left ^= *right); -} - -/// Safely increments the 2-word block counter of the ChaCha20 state. -/// -/// This function increments the lower 32 bits of the counter and, if there is an overflow, -/// increments the upper 32 bits. It checks for overflow in the upper bits and panics if detected. -/// -/// # Arguments -/// * `counter` - A mutable slice of the two 32-bit words forming the block counter. -pub fn safe_2words_counter_increment(counter: &mut [u32]) { - let (lower_word_increment, lower_overflow) = counter[0].overflowing_add(1); - counter[0] = lower_word_increment; - if lower_overflow { - let (higher_word_increment, higher_overflow) = counter[1].overflowing_add(1); - assert!(!higher_overflow, "ChaCha20 block counter overflow"); - counter[1] = higher_word_increment; - } -} - -pub fn safe_2words_counter_increment_n(counter: &mut [u32], n: u64) { - counter[0] = counter[0].wrapping_add(n as u32); - if counter[0] < n as u32 { - counter[1] = counter[1].wrapping_add(1); - } -} - -/// Converts a slice of bytes to a vector of 32-bit unsigned integers. -/// -/// The byte slice is divided into chunks of 4 bytes each, with each chunk being converted -/// to a 32-bit integer. This is commonly used to convert input data into the format needed for ChaCha20 processing. -/// -/// # Arguments -/// * `bytes` - A slice of bytes to be converted. -/// -/// # Returns -/// A vector of 32-bit unsigned integers. -pub fn to_u32_slice(bytes: &[u8]) -> Vec { - bytes - .chunks(4) - .map(|chunk| { - let mut array = [0u8; 4]; - for (dest_elem, src_elem) in array.iter_mut().zip(chunk) { - *dest_elem = *src_elem; - } - u32::from_le_bytes(array) - }) - .collect() -} - -/// Splits a slice of 32-bit words into chunks of 16 words each. -/// -/// This function is typically used to prepare data for processing in the ChaCha20 algorithm, -/// where the state operates on 16-word (512-bit) blocks. -/// -/// # Arguments -/// * `bytes` - A slice of 32-bit words to be chunked. -/// -/// # Returns -/// A vector of 16-word arrays. -pub fn u32_slice_to_16words_chunks(bytes: &[u32]) -> Vec> { - bytes.chunks(16).map(|chunk| chunk.to_vec()).collect() -} - -/// Converts a vector of 32-bit unsigned integers into a vector of bytes. -/// -/// This function is useful for situations where you have a collection of 32-bit integers -/// and need to represent them as a sequence of bytes, such as when preparing data for -/// cryptographic algorithms or for certain IO operations. -/// -/// Each 32-bit integer is split into four bytes in little-endian order. The resulting byte -/// stream concatenates these byte sequences from each integer. -/// -/// # Arguments -/// * `input` - A vector of 32-bit unsigned integers. -/// -/// # Returns -/// A vector of bytes, where each 32-bit integer from the input is represented -/// as four consecutive bytes in the output vector. -pub fn u32_to_u8_vec(input: &[u32]) -> Vec { - input - .into_iter() - .flat_map(|value| value.to_le_bytes().to_vec()) - .collect() -} diff --git a/cipher/src/chacha20/mod.rs b/cipher/src/chacha20/mod.rs deleted file mode 100644 index e370b64..0000000 --- a/cipher/src/chacha20/mod.rs +++ /dev/null @@ -1,111 +0,0 @@ -/// Provides a high-level interface for the ChaCha20 cipher algorithm. -/// -/// This module wraps the lower-level `ChaChaStream` to offer a straightforward API for encryption and decryption. -/// It utilizes the `Cipher` trait for defining common cryptographic operations. -pub mod core; -pub mod stream; - -pub use self::core::{KEY_SIZE, NONCE_SIZE}; -use crate::{Bytes, Cipher, Slice}; -pub use stream::ChaChaStream; - -/// Represents the ChaCha20 encryption/decryption cipher. -/// -/// This struct encapsulates `ChaChaStream` to handle the cipher's state and operations. -/// It provides an interface for initializing the cipher with a key and IV (initialization vector), -/// as well as for performing encryption and decryption operations. -pub struct ChaCha20 { - pub stream: ChaChaStream, -} - -impl ChaCha20 { - /// Constructs a new `ChaCha20` cipher instance. - /// - /// Initializes the internal `ChaChaStream` with the provided key and IV (initialization vector). - /// This setup is necessary for both encryption and decryption operations. - /// - /// # Arguments - /// * `key` - A 256-bit key represented as 32 bytes. - /// * `iv` - A 64-bit IV (nonce) represented as 8 bytes. - /// - /// # Returns - /// A new instance of `ChaCha20`. - pub fn new(key: &[u8], iv: &[u8]) -> Self { - let key: [u8; 32] = match key.len() { - KEY_SIZE => key.try_into().unwrap(), - _ => panic!("Invalid key length"), - }; - - let iv: [u8; 8] = match iv.len() { - NONCE_SIZE => iv.try_into().unwrap(), - _ => panic!("Invalid IV length"), - }; - - Self { - stream: ChaChaStream::new(key, iv), - } - } -} - -impl Cipher for ChaCha20 { - /// Encrypts the provided data using the ChaCha20 algorithm. - /// - /// This method processes the input slice and returns the encrypted data as a byte vector. - /// It utilizes the internal `ChaChaStream` for the encryption process. - /// - /// # Arguments - /// * `data` - A slice of data to be encrypted. - /// - /// # Returns - /// A `Bytes` vector containing the encrypted data. - fn encrypt(&mut self, data: &Slice) -> Bytes { - self.stream.process(data) - } - - /// Decrypts the provided data using the ChaCha20 algorithm. - /// - /// This method processes the input slice and returns the decrypted data as a byte vector. - /// Due to the symmetric nature of the ChaCha20 algorithm, the decryption process is identical to the encryption process. - /// - /// # Arguments - /// * `data` - A slice of encrypted data to be decrypted. - /// - /// # Returns - /// A `Bytes` vector containing the decrypted data. - fn decrypt(&mut self, data: &Slice) -> Bytes { - self.stream.process(data) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const KEY: [u8; 32] = [2; 32]; - const IV: [u8; 8] = [255; 8]; - const PLAINTEXT: [u8; 64] = [0; 64]; - const CIPHERTEXT: [u8; 64] = [ - 252, 239, 233, 9, 94, 65, 152, 139, 167, 124, 231, 72, 105, 220, 6, 152, 193, 163, 210, 194, - 62, 218, 243, 150, 175, 108, 22, 115, 75, 241, 206, 29, 70, 66, 93, 244, 184, 171, 27, 184, - 223, 227, 166, 85, 119, 130, 32, 185, 224, 160, 188, 158, 197, 65, 193, 59, 124, 40, 113, 185, - 82, 103, 124, 182, - ]; - - #[test] - fn it_should_encrypt_data() { - let mut cipher = ChaCha20::new(&KEY, &IV); - let encrypted_data = cipher.encrypt(&PLAINTEXT); - - assert_eq!(encrypted_data.len(), 64); - assert_eq!(encrypted_data, CIPHERTEXT); - } - - #[test] - fn it_should_decrypt_data() { - let mut cipher = ChaCha20::new(&KEY, &IV); - let decrypted_data = cipher.decrypt(&CIPHERTEXT); - - assert_eq!(decrypted_data.len(), 64); - assert_eq!(decrypted_data, PLAINTEXT); - } -} diff --git a/cipher/src/chacha20/stream.rs b/cipher/src/chacha20/stream.rs deleted file mode 100644 index 0015338..0000000 --- a/cipher/src/chacha20/stream.rs +++ /dev/null @@ -1,231 +0,0 @@ -//! This module implements the ChaCha20 block cipher algorithm. -//! It provides the `ChaChaStream` struct for cipher operations, -//! supporting key and nonce initialization, block processing, and state management. - -use std::sync::Arc; - -use crate::{Bytes, Slice}; - -use rayon::prelude::*; - -use super::core::{ - chacha20_rounds, safe_2words_counter_increment, seek_keystream, to_u32_slice, u32_to_u8_vec, xor, - CONSTANTS, STATE_WORDS, -}; - -/// The number of blocks to process in each parallel encryption thread. -const PARALLEL_BLOCKS: usize = 32; - -/// The number of words that each thread should process. -/// Each block is 64 bytes long, and each word is 4 bytes long. -/// Therefore, each thread manages up to 16.384 bytes of data. -/// TODO: This should be calculated dynamically based on the number of threads. -const WORDS_PER_THREAD: usize = PARALLEL_BLOCKS * STATE_WORDS; - -/// A ChaCha20 cipher stream. -/// -/// This struct represents a single state of the ChaCha20 cipher, -/// encapsulating a 512-bit state (16 words of 32 bits each) and a keystream of the same size. -pub struct ChaChaStream { - pub state: [u32; 16], - pub keystream: [u32; 16], -} - -impl ChaChaStream { - /// Constructs a new `ChaChaStream`. - /// - /// Initializes the ChaCha20 cipher state with the provided key and IV (initialization vector). - /// The state is set up as per the ChaCha20 specification with constants, key, block counter set to zero, and nonce. - /// - /// # Arguments - /// * `key` - A 256-bit key represented as 32 bytes. - /// * `iv` - A 64-bit IV (nonce) represented as 8 bytes. - /// - /// # Returns - /// A new `ChaChaStream` with the initialized state. - pub fn new(key: [u8; 32], iv: [u8; 8]) -> Self { - // Initialize an array of 16 unsigned 32-bit integers (u32) to represent the cipher's state. - let mut state = [0u32; STATE_WORDS]; - - // The first four words (16 bytes) of the state are set to the ChaCha20 constant. - // This constant is the ASCII string "expand 32-byte k", used for creating the initial state. - state[0..4].copy_from_slice(&CONSTANTS); - - // The next eight words (32 bytes) of the state are set to the encryption key. - // The key is divided into 8 chunks, each containing 4 bytes (32 bits). - // Each chunk is then converted from little-endian byte order to a u32 and stored in the state array. - let key_chunks = key.chunks_exact(4); - for (val, chunk) in state[4..12].iter_mut().zip(key_chunks) { - *val = u32::from_le_bytes(chunk.try_into().unwrap()); - } - - // The block counter occupies the next two words (13th and 14th positions) in the state. - // In ChaCha20, this counter is used to make each block unique. - // We skip those they are set to zero already. - // Here, we use the last 8-byte space of the block for the IV (initialization vector). - let iv_chunks = iv.chunks_exact(4); - for (val, chunk) in state[14..16].iter_mut().zip(iv_chunks) { - *val = u32::from_le_bytes(chunk.try_into().unwrap()); - } - - // Finally, the constructed state array is cloned into the keystream. - // The keystream will be used in the cipher operations and will be modified independently of the initial state. - Self { - state, - keystream: state.clone(), - } - } - - /// Adds a block to the keystream. - /// - /// Updates the keystream by applying the ChaCha20 cipher rounds to the current state, - /// then increments the block counter. - pub fn add_block(&mut self) { - self.keystream = self.state.clone(); - - chacha20_rounds(&mut self.keystream, Some(self.state)); - - safe_2words_counter_increment(&mut self.state[12..14]); - } - - /// Processes the provided data using the ChaCha20 cipher. - /// - /// Encrypts or decrypts the input bytes by XORing them with the keystream generated by the cipher. - /// This method is suitable for both encryption and decryption. - /// Input Bytes: | Byte 0 | Byte 1 | Byte 2 | ... | Byte 63 | - /// |<----------- 64 bytes (512 bits) --------->| - /// - /// Step 1: Convert input bytes to 32-bit words - /// 32-bit Words: | Word 0 | Word 1 | Word 2 | ... | Word 15 | - /// |<-------- 16 words (64 bytes) ----------->| - /// - /// Step 2: Process each 64-byte block - /// For each block: - /// - Call `self.add_block()` to update the keystream for the current block. - /// - /// - XOR each byte of the block with the corresponding byte in the keystream. - /// (The keystream is generated from the ChaCha20 state and is also 64 bytes long.) - /// - /// Processing: | Word 0 | Word 1 | ... | Word 15 | - /// Input Block: | in[0..3] | in[4..7] | ... | in[60..63] | - /// Keystream: | ks[0..3] | ks[4..7] | ... | ks[60..63] | - /// XOR Operation: | out[0..3] | out[4..7] | ... | out[60..63] | - /// | (in XOR ks) | (in XOR ks) | ... | (in XOR ks) | - /// - /// Step 3: Clear the keystream after processing all blocks - /// `self.clear_stream()` - /// - /// Step 4: Convert the processed 32-bit words back to bytes - /// Output Bytes: | Byte 0 | Byte 1 | Byte 2 | ... | Byte 63 | - /// - /// # Arguments - /// * `bytes_in` - A slice of bytes to be processed. - /// - /// # Returns - /// A byte vector containing the processed data. - pub fn process(&mut self, bytes_in: &Slice) -> Bytes { - // Prepare the output vector of 32-bit words from the input - let mut out: Vec = to_u32_slice(bytes_in); - // Wrap the state in an Arc to allow for parallel processing - let arc_state = Arc::new(self.state); - - // Process each chunk of 8 blocks in parallel - out - .par_chunks_mut(WORDS_PER_THREAD) - .enumerate() - .for_each(|(i, blocks_chunk)| { - blocks_chunk - .chunks_mut(STATE_WORDS) - .enumerate() - .for_each(|(j, block)| { - // Cipher each 64-byte block in the chunk - let chunk_keystream = seek_keystream(&arc_state, (i * PARALLEL_BLOCKS + j) as u64); - xor(block, &chunk_keystream); - }); - }); - - // Clear the keystream - self.clear_stream(); - - // Convert the output to a byte vector - let mut bytes_out = u32_to_u8_vec(&out); - // Truncate the output to the length of the input, - // as the output may contain extra bytes if the input length is not a multiple of 64 - bytes_out.truncate(bytes_in.len()); - bytes_out - } - - fn clear_stream(&mut self) { - // Clear the keystream - self.keystream = [0u32; 16]; - // Reset the block counter - self.state[12] = 0; - self.state[13] = 0; - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const PLAINTEXT: &[u8; 100] = b"Nel mezzo del cammin di nostra vita mi ritrovai per una selva oscura che la diritta via era smarrita"; - const KEY: [u8; 32] = [ - 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, - 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - ]; - const CIPHERTEXT: [u8; 100] = [ - 146, 150, 64, 198, 111, 122, 166, 62, 64, 94, 27, 29, 148, 164, 141, 105, 199, 22, 190, 223, - 141, 210, 230, 218, 222, 247, 213, 231, 21, 24, 81, 93, 64, 103, 103, 96, 138, 0, 130, 225, 55, - 151, 65, 97, 250, 172, 209, 20, 9, 197, 0, 50, 177, 208, 241, 189, 105, 124, 63, 147, 39, 218, - 221, 241, 252, 156, 130, 147, 207, 121, 84, 225, 138, 201, 229, 31, 254, 87, 183, 196, 224, - 133, 32, 87, 225, 118, 59, 0, 115, 54, 126, 60, 122, 35, 3, 45, 150, 13, 11, 66, - ]; - const IV: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]; - - #[test] - fn it_constructs_an_unencrypted_chacha_block() { - let chacha20 = ChaChaStream::new(KEY, IV); - - assert_eq!(chacha20.state.len(), 16); - assert_eq!(chacha20.state[0..4], CONSTANTS); - assert_eq!( - chacha20.state[4..12], - [67305985, 134678021, 202050057, 269422093, 336794129, 404166165, 471538201, 538910237] - ); - assert_eq!(chacha20.state[12], 0); - assert_eq!(chacha20.state[13], 0); - assert_eq!(chacha20.state[14], 67305985); - assert_eq!(chacha20.state[15], 134678021); - } - - #[test] - fn it_encrypts_data() { - let mut chacha20 = ChaChaStream::new(KEY, IV); - - let encrypted_data = chacha20.process(PLAINTEXT); - - assert_eq!(encrypted_data, CIPHERTEXT); - } - - #[test] - fn it_can_reverse_encryption() { - let mut chacha20 = ChaChaStream::new([1u8; 32], [2u8; 8]); - let data = [0u8; 64]; - - let encrypted_data = chacha20.process(&data); - let decrypted_data = chacha20.process(&encrypted_data); - - assert_eq!(decrypted_data, data); - } - - #[test] - fn it_can_reverse_encryption_for_data_smaller_than_a_chunk() { - let mut chacha20 = ChaChaStream::new([1u8; 32], [2u8; 8]); - let data = [0u8; 1]; - - let encrypted_data = chacha20.process(&data); - let decrypted_data = chacha20.process(&encrypted_data); - - assert_eq!(decrypted_data, data); - } -} diff --git a/cipher/src/lib.rs b/cipher/src/lib.rs index 8d5f3ec..2f96632 100644 --- a/cipher/src/lib.rs +++ b/cipher/src/lib.rs @@ -1,97 +1,129 @@ //! # Secured-Cipher Library //! -//! This library, `secured-cipher`, provides an implementation of the ChaCha20 encryption algorithm, -//! along with a common cryptographic interface defined by the `Cipher` trait. -//! The library is structured to offer both low-level and high-level cryptographic functionalities. +//! `secured-cipher` is a Rust library offering an implementation of the ChaCha20 and XChaCha20 encryption algorithms. +//! It provides both high-level and low-level cryptographic functionalities through a common interface. //! //! ## Overview //! -//! - `ChaCha20`: A struct that provides a high-level interface for the ChaCha20 stream cipher algorithm. -//! It offers methods for encryption and decryption operations, simplifying the use of the underlying `ChaChaStream`. -//! - `ChaChaStream`: A struct that handles the core state and operations of the ChaCha20 cipher. -//! It is used internally by `ChaCha20` but can also be used directly for lower-level control. -//! - `Cipher`: A trait that defines a standard interface for cryptographic operations, -//! specifically focusing on encryption and decryption methods. -//! - Type aliases (`Slice`, `Key`, `Nonce`, `Bytes`): These simplify the usage of common cryptographic data types -//! such as keys, nonces, and byte slices. +//! The library includes the following key components: +//! +//! - `core`: A module containing essential ChaCha20 cryptographic functionalities. +//! - `ChaCha20`: A struct for the ChaCha20 stream cipher algorithm. +//! - `Cipher`: A struct that provides a common interface for cryptographic operations, focusing on encryption and decryption. +//! - `CipherMode`: An enum to specify the mode of the cipher (only ChaCha20 for now). +//! +//! ## Features +//! +//! - High-level interfaces for ChaCha20 and XChaCha20 ciphers. +//! - Common `Cipher` interface for encryption and decryption operations. +//! - Flexible usage with support for both raw and high-level cryptographic operations. //! //! ## Usage //! -//! ### Encrypting Data with ChaCha20 +//! ### Basic Encryption and Decryption +//! +//! This example demonstrates encrypting and decrypting data using the ChaCha20 cipher. //! //! ```rust -//! use secured_cipher::{chacha20::ChaCha20, Cipher, Slice}; +//! use secured_cipher::Cipher; //! -//! let key: [u8; 32] = [0; 32]; // Replace with your key -//! let nonce: [u8; 8] = [0; 8]; // Replace with your nonce +//! let key: [u8; 32] = [0; 32]; // Your key +//! let nonce: [u8; 12] = [0; 12]; // Your nonce //! let data: &[u8] = b"Your data here"; // Data to be encrypted //! -//! let mut cipher = ChaCha20::new(&key, &nonce); +//! let mut cipher = Cipher::default(); +//! cipher.init(&key, &nonce); +//! //! let encrypted_data = cipher.encrypt(data); //! println!("Encrypted data: {:?}", encrypted_data); -//! ``` -//! -//! ### Decrypting Data with ChaCha20 -//! -//! ```rust -//! use secured_cipher::{chacha20::ChaCha20, Cipher, Slice}; -//! -//! let key: [u8; 32] = [0; 32]; // Replace with your key -//! let nonce: [u8; 8] = [0; 8]; // Replace with your nonce -//! let encrypted_data: &[u8] = &[0x1, 0x2, 0x3, 0x4]; // Replace with your encrypted data //! -//! let mut cipher = ChaCha20::new(&key, &nonce); -//! let decrypted_data = cipher.decrypt(encrypted_data); +//! let decrypted_data = cipher.decrypt(&encrypted_data); //! println!("Decrypted data: {:?}", decrypted_data); //! ``` //! //! ## Modules //! -//! - `core`: Contains core functionalities and algorithmic implementations. -//! - `stream`: Provides the `ChaChaStream` struct and related functions for internal stream cipher operations. -//! -//! This library aims to provide an easy-to-use and efficient implementation of the ChaCha20 cipher, -//! suitable for various cryptographic needs. Whether you need high-level interfaces with `ChaCha20` -//! or low-level control with `ChaChaStream`, `secured-cipher` is equipped to meet your cryptographic requirements. +//! - `core`: Core functionalities and algorithmic implementations. +//! - `stream`: Internal stream cipher operations, including `ChaChaStream`. + +pub mod permutation; -pub mod chacha20; pub use secured_cipher_key::{random_bytes, Key}; -// pub use chacha20; -/// A type alias for representing a slice of bytes. -/// This is commonly used for raw data input/output in cryptographic operations. -pub type Slice = [u8]; +pub use permutation::{ChaCha20, Permutation}; -/// Type alias for unencrypted and encrypted data represented as a vector of bytes. -/// This is used for the output of cryptographic operations, like the result of encryption or decryption. -pub type Bytes = Vec; +/// 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` trait defines a common interface for cryptographic operations, specifically encryption and decryption. -/// -/// This trait is meant to be implemented by specific cryptographic algorithms, providing a consistent interface -/// for performing these operations across different types of ciphers. -pub trait Cipher { - /// Encrypts the provided data. +pub enum CipherMode { + ChaCha20, + // TODO: XChaCha20, +} + +impl Cipher { + /// Constructs a new `Cipher` instance using the specified cipher mode. /// - /// Takes a slice of data (`&Slice`) and returns an encrypted version of it as a vector of bytes (`Bytes`). - /// The exact nature of the encryption depends on the implementation of the trait. + /// # Arguments + /// * `mode` - The mode of cipher (ChaCha20 or XChaCha20) to use. + /// + /// # Returns + /// A new instance of `Cipher`. + pub fn new(mode: CipherMode) -> Self { + let permutation: Box = match mode { + CipherMode::ChaCha20 => Box::new(ChaCha20::new()), + // TODO: CipherMode::XChaCha20 => Box::new(XChaCha20::new()), + }; + + Self { permutation } + } + + /// Initializes the cipher with a key and IV (initialization vector). + /// Sets up the cipher's internal state for encryption or decryption. + /// + /// # Arguments + /// * `key` - A byte slice representing the key. + /// * `iv` - A byte slice representing the initialization vector. + /// + /// # 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 + } + + /// Encrypts the provided data. /// /// # Arguments /// * `data` - A slice of data to be encrypted. /// /// # Returns - /// Encrypted data as a vector of bytes. - fn encrypt(&mut self, data: &Slice) -> Bytes; + /// Encrypted data as a vector of bytes (`Bytes`). + pub fn encrypt(&mut self, data: &[u8]) -> Vec { + self.permutation.process(data) + } /// Decrypts the provided data. /// - /// Takes a slice of encrypted data (`&Slice`) and returns a decrypted version of it as a vector of bytes (`Bytes`). - /// The decryption process depends on the specific implementation of the trait and should reverse the effects of the corresponding `encrypt` method. - /// /// # Arguments /// * `data` - A slice of encrypted data to be decrypted. /// /// # Returns - /// Decrypted data as a vector of bytes. - fn decrypt(&mut self, data: &Slice) -> Bytes; + /// Decrypted data as a vector of bytes (`Bytes`). + pub fn decrypt(&mut self, data: &[u8]) -> Vec { + self.permutation.process(data) + } +} + +impl Default for Cipher { + /// Provides a default instance of `Cipher` using the XChaCha20 mode. + /// + /// # Returns + /// A new instance of `Cipher` with XChaCha20 mode. + fn default() -> Self { + Self::new(CipherMode::ChaCha20) + } } diff --git a/cipher/src/permutation/chacha20.rs b/cipher/src/permutation/chacha20.rs new file mode 100644 index 0000000..63a2486 --- /dev/null +++ b/cipher/src/permutation/chacha20.rs @@ -0,0 +1,281 @@ +use crate::permutation::core::CHACHA20_NONCE_SIZE; + +use super::core::{permute, xor_bytes, Block, CONSTANTS, STATE_WORDS}; + +use super::Permutation; + +/// The `ChaCha20` struct represents the ChaCha20 stream cipher. +pub struct ChaCha20 { + state: Block, +} + +impl ChaCha20 { + /// Constructs a new `ChaCha20` cipher instance. + /// + /// This function initializes the internal state of the cipher. + /// + /// # Returns + /// A new instance of `ChaCha20`. + pub fn new() -> Self { + Self { + state: [0u32; STATE_WORDS], + } + } + + /// Generates the next 64-byte keystream block from the ChaCha20 state. + /// + /// This function advances the ChaCha20 state and produces a keystream block based on the current state. + /// It performs a permutation of the state, increments the block counter to ensure uniqueness for subsequent calls, + /// and then serializes the permuted state into a 64-byte array. + /// + /// # Returns + /// A 64-byte array representing the generated keystream block. + /// + /// # Panics + /// Panics if the 32-bit block counter overflows, which would only happen after a very large + /// number of blocks (2^32-1) have been processed with the same key-nonce combination. + /// + /// # Example + /// ``` + /// use secured_cipher::{ChaCha20, Permutation}; + /// + /// let mut chacha20 = ChaCha20::new(); + /// chacha20.init(&[0_u8; 32], &[0_u8; 12]); + /// + /// let keystream_block = chacha20.next_keystream(); + /// // `keystream_block` now contains the next 64 bytes of the keystream + /// ``` + /// + /// # Notes + /// The keystream generated by this function is used to encrypt or decrypt data by XORing + /// it with the plaintext or ciphertext. Each call to this function must produce a unique keystream block. + /// This uniqueness is guaranteed by incrementing the internal block counter. + pub fn next_keystream(&mut self) -> [u8; 64] { + // Ensures the block counter has not overflowed + assert!(self.state[12] != 0, "ChaCha20 counter overflow"); + + // Initialize an array to hold the keystream + let mut keystream = [0u8; 64]; + + // Perform the ChaCha20 permutation on the current state + let block = permute(&self.state); + + // Increment the block counter, wrapping around if it reaches its maximum value + self.state[12] = self.state[12].wrapping_add(1); + + // 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 { + /// 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, + /// a zeroed block counter, and the provided IV. + /// + /// # Arguments + /// * `key` - A 256-bit key represented as 32 bytes. + /// * `iv` - A 86-bit IV (nonce) represented as 12 bytes. + fn init(&mut self, key: &[u8], iv: &[u8]) { + // The key must be 256 bits (32 bytes) long, and the IV must be 64 bits (8 bytes) long. + assert!(key.len() == 32); + assert!(iv.len() == CHACHA20_NONCE_SIZE); + + // The first four words (16 bytes) of the state are set to the ChaCha20 constant. + // This constant is the ASCII string "expand 32-byte k", used for creating the initial state. + self.state[0..4].copy_from_slice(&CONSTANTS); + + // The next eight words (32 bytes) of the state are set to the encryption key. + // The key is divided into 8 chunks, each containing 4 bytes (32 bits). + // Each chunk is then converted from little-endian byte order to a u32 and stored in the state array. + let key_chunks = key.chunks_exact(4); + for (val, chunk) in self.state[4..12].iter_mut().zip(key_chunks) { + *val = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + // The block counter occupies the next word (13th positions) in the state. + // In ChaCha20, this counter is used to make each block unique. + self.state[12] = 1; + + // Here, we use the last 8-byte space of the block for the IV (initialization vector). + let iv_chunks = iv.chunks_exact(4); + for (val, chunk) in self.state[13..16].iter_mut().zip(iv_chunks) { + *val = u32::from_le_bytes(chunk.try_into().unwrap()); + } + } + + /// Processes input data using the ChaCha20 cipher algorithm. + /// + /// This function applies the ChaCha20 encryption or decryption process to the given input bytes. + /// It works by generating a unique keystream for each 64-byte block of the input data and then + /// applying an XOR operation between the data block and the keystream. This process is suitable + /// for both encryption and decryption due to the reversible nature of the XOR operation. + /// + /// # Arguments + /// * `bytes_in` - A slice of bytes representing the input data to be processed (either plaintext for encryption + /// or ciphertext for decryption). + /// + /// # 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}; + /// + /// let mut chacha20 = ChaCha20::new(); + /// chacha20.init(&[0_u8; 32], &[0_u8; 12]); + /// + /// let data = b"some plaintext data"; // Data to be encrypted or decrypted + /// let processed_data = chacha20.process(data); + /// // `processed_data` now contains the encrypted or decrypted output + /// ``` + /// + /// # Notes + /// It's important to use the same nonce and key for decrypting the data that were used for encryption. + /// The output size will be equal to the input size, as ChaCha20 is a stream cipher. + fn process(&mut self, bytes_in: &[u8]) -> Vec { + // Clone the input bytes to prepare the output vector + 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(); + + // 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; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const PLAINTEXT: [u8; 114] = [ + 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, + 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, + 0x74, 0x2e, + ]; + const KEY: [u8; 32] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + ]; + const CIPHERTEXT: [u8; 114] = [ + 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, + 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, + 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, + 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8, + 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e, + 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36, + 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42, + 0x87, 0x4d, + ]; + const IV: [u8; CHACHA20_NONCE_SIZE] = [ + 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, + ]; + + #[test] + fn it_correctly_inits_the_chacha20_state() { + let mut chacha20 = ChaCha20::new(); + chacha20.init(&KEY, &IV); + + assert_eq!( + chacha20.state, + [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908, + 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000001, 0x09000000, + 0x4a000000, 0x00000000 + ] + ); + } + + #[test] + fn it_gets_the_first_keystream() { + let mut chacha20 = ChaCha20::new(); + chacha20.init(&KEY, &IV); + + let block = chacha20.next_keystream(); + + assert_eq!( + block, + [ + 0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71, + 0xc4, 0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a, 0xc3, 0xd4, + 0x6c, 0x4e, 0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2, 0xd7, 0x05, 0xd9, + 0x8b, 0x02, 0xa2, 0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, 0xcb, 0xd0, 0x83, 0xe8, + 0xa2, 0x50, 0x3c, 0x4e, + ] + ); + } + + #[test] + fn it_encrypts_data() { + let mut chacha20 = ChaCha20::new(); + chacha20.init( + &KEY, + &[ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, + ], + ); + + let encrypted_data = chacha20.process(&PLAINTEXT); + + assert_eq!(encrypted_data, CIPHERTEXT); + } + + #[test] + fn it_can_reverse_encryption() { + let mut chacha20 = ChaCha20::new(); + chacha20.init(&[1u8; 32], &[2u8; CHACHA20_NONCE_SIZE]); + let data = [0u8; 64]; + + let encrypted_data = chacha20.process(&data); + let decrypted_data = chacha20.process(&encrypted_data); + + assert_eq!(decrypted_data, data); + } + + #[test] + fn it_can_reverse_encryption_for_data_smaller_than_a_chunk() { + let mut chacha20 = ChaCha20::new(); + chacha20.init(&[1u8; 32], &[2u8; CHACHA20_NONCE_SIZE]); + let data = [0u8; 1]; + + let encrypted_data = chacha20.process(&data); + let decrypted_data = chacha20.process(&encrypted_data); + + assert_eq!(decrypted_data, data); + } +} diff --git a/cipher/src/permutation/core.rs b/cipher/src/permutation/core.rs new file mode 100644 index 0000000..42d8f21 --- /dev/null +++ b/cipher/src/permutation/core.rs @@ -0,0 +1,186 @@ +//! ChaCha20 Cryptographic Algorithm Implementation +//! +//! This module provides an implementation of the ChaCha20 stream cipher, +//! as specified in RFC 7539 by the Internet Engineering Task Force (IETF). +//! The implementation includes constants, state management, encryption/decryption functions, +//! and utilities for data transformation relevant to the ChaCha20 algorithm. +//! +//! The constants and logic, as well as the test vectors used in this module, +//! are based on and verifiable against the specifications detailed in the IETF paper: +//! "ChaCha20 and Poly1305 for IETF Protocols" (RFC 7539). +//! This can be accessed at https://datatracker.ietf.org/doc/html/rfc7539. +//! +//! The module is designed to be compliant with the RFC 7539 standard, ensuring reliability +//! and correctness of the cryptographic operations as per the established IETF guidelines. + +/// Constants for the ChaCha20 algorithm. +/// These four 32-bit words represent the ASCII encoding of "expand 32-byte k", +/// used in the state initialization of the ChaCha20 block. +pub const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]; + +/// Number of 32-bit words in the ChaCha state. +/// The ChaCha20 state consists of 16 words, each of which is 32 bits long. +pub const STATE_WORDS: usize = 16; + +/// Number of ChaCha20 rounds. +/// This constant defines how many rounds of the main ChaCha20 algorithm will be executed. +/// The standard number of rounds is 20. +pub const ROUNDS: usize = 10; + +/// Size of the ChaCha20 nonce in bytes. +/// The nonce is a 64-bit (8 bytes) value used to make each block unique. +pub const CHACHA20_NONCE_SIZE: usize = 12; + +/// Size of the XChaCha20 nonce in bytes. +/// The nonce is a 128-bit (16 bytes) value used to make each block unique. +pub const XCHACHA20_NONCE_SIZE: usize = 16; + +/// Size of the key in bytes. +/// The key is a 256-bit (32 bytes) value used for encryption and decryption. +pub const KEY_SIZE: usize = 32; + +/// The array of words representing a ChaCha20 block. +pub type Block = [u32; STATE_WORDS]; + +/// Performs the quarter round operation on the state. +/// +/// This operation modifies four words in the state as per the ChaCha20 algorithm's quarter round rules. +/// It involves a series of addition, XOR, and rotation operations to mix the input words. +/// +/// # Arguments +/// * `a`, `b`, `c`, `d` - Indices of the state words to be modified. +/// * `state` - A mutable reference to the 512-bit state array. +pub fn quarter_round(a: usize, b: usize, c: usize, d: usize, state: &mut Block) { + state[a] = state[a].wrapping_add(state[b]); + state[d] ^= state[a]; + state[d] = state[d].rotate_left(16); + + state[c] = state[c].wrapping_add(state[d]); + state[b] ^= state[c]; + state[b] = state[b].rotate_left(12); + + state[a] = state[a].wrapping_add(state[b]); + state[d] ^= state[a]; + state[d] = state[d].rotate_left(8); + + state[c] = state[c].wrapping_add(state[d]); + state[b] ^= state[c]; + state[b] = state[b].rotate_left(7); +} + +/// Runs the ChaCha20 permutation on the provided state. +pub fn permute(state: &Block) -> Block { + let mut block = state.clone(); + + // The ChaCha20 permutation consists of 20 rounds of quarter round operations. + run_rounds(&mut block); + + // The original ChaCha20 algorithm adds the original state to the output of the rounds. + for (s1, s0) in block.iter_mut().zip(state.iter()) { + *s1 = s1.wrapping_add(*s0); + } + + block +} + +/// Runs the ChaCha20 rounds on the provided state. +/// This function modifies the state in place. +pub fn run_rounds(state: &mut Block) { + for _ in 0..ROUNDS { + // Odd rounds + quarter_round(0, 4, 8, 12, state); + quarter_round(1, 5, 9, 13, state); + quarter_round(2, 6, 10, 14, state); + quarter_round(3, 7, 11, 15, state); + // Even rounds + quarter_round(0, 5, 10, 15, state); + quarter_round(1, 6, 11, 12, state); + quarter_round(2, 7, 8, 13, state); + quarter_round(3, 4, 9, 14, state); + } +} + +/// XORs two 512-bit state arrays. +/// This function modifies the first array in place. +/// +/// # Arguments +/// * `a` - A mutable reference to the first state array. +/// * `b` - A reference to the second state array. +/// +/// # Panics +/// Panics if the two arrays are not of equal length. +pub fn xor_bytes(left: &mut [u8], right: &[u8]) { + assert!( + right.len() >= left.len(), + "The left array can't be XORed completely with the right array" + ); + left + .iter_mut() + .zip(right.iter()) + .for_each(|(left, right)| *left ^= *right); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn it_should_do_the_quarter_round() { + let mut state: Block = [ + 0x879531e0, 0xc5ecf37d, 0x516461b1, 0xc9a62f8a, 0x44c20ef3, 0x3390af7f, 0xd9fc690b, + 0x2a5f714c, 0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963, 0x5c971061, 0x3d631689, + 0x2098d9d6, 0x91dbd320, + ]; + + quarter_round(2, 7, 8, 13, &mut state); + + assert_eq!( + state, + [ + 0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a, 0x44c20ef3, 0x3390af7f, 0xd9fc690b, + 0xcfacafd2, 0xe46bea80, 0xb00a5631, 0x974c541a, 0x359e9963, 0x5c971061, 0xccc07c79, + 0x2098d9d6, 0x91dbd320, + ] + ); + } + + #[test] + fn it_runs_all_the_quarter_rounds() { + let mut state: Block = [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908, + 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000001, 0x09000000, + 0x4a000000, 0x00000000, + ]; + + run_rounds(&mut state); + + assert_eq!( + state, + [ + 0x837778ab, 0xe238d763, 0xa67ae21e, 0x5950bb2f, 0xc4f2d0c7, 0xfc62bb2f, 0x8fa018fc, + 0x3f5ec7b7, 0x335271c2, 0xf29489f3, 0xeabda8fc, 0x82e46ebd, 0xd19c12b4, 0xb04e16de, + 0x9e83d0cb, 0x4e3c50a2, + ] + ); + } + + #[test] + fn it_executes_the_chacha20_permutation() { + let state: Block = [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908, + 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000001, 0x09000000, + 0x4a000000, 0x00000000, + ]; + + let result = permute(&state); + + assert_eq!( + result, + [ + 0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3, 0xc7f4d1c7, 0x0368c033, 0x9aaa2204, + 0x4e6cd4c3, 0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9, 0xd19c12b5, 0xb94e16de, + 0xe883d0cb, 0x4e3c50a2, + ] + ); + } +} diff --git a/cipher/src/permutation/mod.rs b/cipher/src/permutation/mod.rs new file mode 100644 index 0000000..a3e3eac --- /dev/null +++ b/cipher/src/permutation/mod.rs @@ -0,0 +1,42 @@ +/// Core module containing essential cryptographic functionalities. +pub mod core; + +/// Module for the ChaCha20 stream cipher algorithm. +pub mod chacha20; + +/// Re-exporting `ChaCha20` for direct use. +pub use chacha20::ChaCha20; + +/// `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). + /// + /// This method sets up the internal state of the cipher using the provided key and IV, + /// preparing it for either encryption or decryption. + /// + /// # Arguments + /// * `key` - A byte slice representing the cryptographic key. + /// * `iv` - A byte slice representing the initialization vector. + fn init(&mut self, key: &[u8], iv: &[u8]); + + /// 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 + /// 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(&mut self, data: &[u8]) -> Vec; + + /// Clears the internal state of the cipher. + /// + /// 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); +} diff --git a/enclave/src/lib.rs b/enclave/src/lib.rs index 14429e6..c7fa701 100644 --- a/enclave/src/lib.rs +++ b/enclave/src/lib.rs @@ -2,13 +2,12 @@ pub mod errors; pub use errors::EnclaveError; use secured_cipher::{ - chacha20::{ - core::{KEY_SIZE, NONCE_SIZE}, - ChaCha20, - }, + permutation::core::{KEY_SIZE, CHACHA20_NONCE_SIZE}, random_bytes, Cipher, }; +const NONCE_SIZE: usize = CHACHA20_NONCE_SIZE; + /// `Enclave` acts as a container for encrypted data, including metadata and the encrypted content itself. /// /// Metadata is unencrypted and can be used to store information about the data, @@ -44,9 +43,7 @@ impl Enclave { plain_bytes: Vec, ) -> Result { let nonce = random_bytes::(); - let mut cipher = ChaCha20::new(&key, &nonce); - - let encrypted_bytes = cipher.encrypt(&plain_bytes); + let encrypted_bytes = Cipher::default().init(&key, &nonce).encrypt(&plain_bytes); Ok(Enclave { metadata, @@ -63,9 +60,11 @@ impl Enclave { /// # Returns /// A `Result` containing the decrypted data as a vector of bytes, or an error string if decryption fails. pub fn decrypt(&self, key: [u8; KEY_SIZE]) -> Result, String> { - let mut cipher = ChaCha20::new(&key, &self.nonce); - - Ok(cipher.decrypt(&self.encrypted_bytes)) + Ok( + Cipher::default() + .init(&key, &self.nonce) + .decrypt(&self.encrypted_bytes), + ) } }