quasor 6.1.6

A high-security AEAD based on a Duplex Sponge construction with SHAKE256, Argon2id, and BLAKE3.
Documentation
// crates/quasor/src/parallel.rs

use crate::quasor::{Error, Quasor};
use rayon::prelude::*;

// Each chunk will be 1MB. This is a good size for parallelization.
const CHUNK_SIZE: usize = 1024 * 1024;

impl Quasor {
    /// Encrypts data in parallel using a chunked approach.
    /// Returns a tuple of (ciphertext, chunk_tags, chunk_nonces).
    pub fn encrypt_parallel(
        &self,
        plaintext: &[u8],
        ad: &[u8],
    ) -> (Vec<u8>, Vec<Vec<u8>>, Vec<Vec<u8>>) {
        let results: Vec<(Vec<u8>, Vec<u8>, Vec<u8>)> = plaintext
            .par_chunks(CHUNK_SIZE)
            .enumerate()
            .map(|(i, chunk)| {
                // 1a. Derive a unique key for this chunk
                let mut chunk_key_hasher = blake3::Hasher::new_keyed(self.master_key());
                chunk_key_hasher.update(b"chunk-key");
                chunk_key_hasher.update(&(i as u64).to_le_bytes());
                let chunk_key: [u8; 32] = chunk_key_hasher.finalize().into();

                // 1b. Encrypt the chunk with its unique key
                let chunk_quasor = Quasor::from_raw_key(chunk_key);
                let (ct, tag, nonce) = chunk_quasor.encrypt(chunk, ad);

                (ct, tag, nonce)
            })
            .collect();

        // 2. Separate the ciphertexts, tags, and nonces
        let mut ciphertexts = Vec::with_capacity(results.len());
        let mut tags = Vec::with_capacity(results.len());
        let mut nonces = Vec::with_capacity(results.len());
        for (ct, tag, nonce) in results {
            ciphertexts.push(ct);
            tags.push(tag);
            nonces.push(nonce);
        }

        // 3. Flatten the ciphertext chunks into a single vector.
        let ciphertext = ciphertexts.into_iter().flatten().collect();

        (ciphertext, tags, nonces)
    }

    /// Decrypts data in parallel using a chunked approach.
    pub fn decrypt_parallel(
        &self,
        ciphertext: &[u8],
        ad: &[u8],
        chunk_tags: &[Vec<u8>],
        chunk_nonces: &[Vec<u8>],
    ) -> Result<Vec<u8>, Error> {
        let plaintexts: Result<Vec<Vec<u8>>, Error> = ciphertext
            .par_chunks(CHUNK_SIZE)
            .zip(chunk_tags.par_iter())
            .zip(chunk_nonces.par_iter())
            .enumerate()
            .map(|(i, ((chunk, tag), nonce))| {
                // 1a. Derive a unique key for this chunk
                let mut chunk_key_hasher = blake3::Hasher::new_keyed(self.master_key());
                chunk_key_hasher.update(b"chunk-key");
                chunk_key_hasher.update(&(i as u64).to_le_bytes());
                let chunk_key: [u8; 32] = chunk_key_hasher.finalize().into();

                // 1b. Decrypt the chunk with its unique key, nonce, and tag
                let chunk_quasor = Quasor::from_raw_key(chunk_key);
                chunk_quasor.decrypt(nonce, chunk, ad, tag)
            })
            .collect();

        // If any chunk failed decryption, the whole process fails.
        let plaintexts = plaintexts?;

        // Flatten the plaintext chunks into a single vector.
        let plaintext = plaintexts.into_iter().flatten().collect();

        Ok(plaintext)
    }
}