Skip to content

Commit

Permalink
feat: add parallelism and inplace encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
mikesposito committed Dec 18, 2023
1 parent 3caed95 commit 7b1042e
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 96 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ version = "^7.3.1"
[[bin]]
name = "secured"
path = "src/main.rs"

[profile.bench]
debug = true
3 changes: 3 additions & 0 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ secured-cipher = { path = "../cipher/" }
name = "chacha20"
path = "src/chacha20.rs"
harness = false

[profile.bench]
debug = true
46 changes: 38 additions & 8 deletions benches/src/chacha20.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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();
Expand Down
3 changes: 3 additions & 0 deletions cipher/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
}
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -123,18 +150,9 @@ impl Permutation for ChaCha20 {
/// # Returns
/// A `Vec<u8>` 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]);
Expand All @@ -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::*;
Expand Down Expand Up @@ -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);
}
}
41 changes: 31 additions & 10 deletions cipher/src/permutation/mod.rs → cipher/src/algorithm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -36,10 +45,22 @@ pub trait Permutation {
/// # Returns
/// A vector of bytes representing the processed data.
fn process(&mut self, data: &[u8]) -> Vec<u8>;
}

/// 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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 7b1042e

Please sign in to comment.