// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![allow(missing_docs, unused_results, clippy::indexing_slicing, clippy::unwrap_used)] use criterion::{black_box, criterion_group, criterion_main, Criterion}; use crypto_provider::{CryptoProvider, CryptoRng}; use crypto_provider_rustcrypto::RustCrypto; use ctr::cipher::{KeyIvInit as _, StreamCipher as _, StreamCipherSeek as _}; use ldt::{ DefaultPadder, LdtCipher, LdtDecryptCipher, LdtEncryptCipher, LdtKey, Mix, Padder, Swap, XorPadder, }; use ldt_tbc::TweakableBlockCipher; use sha2::Digest as _; use std::marker; use subtle::ConstantTimeEq as _; use xts_aes::{XtsAes128, XtsAes256}; pub fn ldt_scan(c: &mut Criterion) { for &num_keys in &[1_usize, 10, 1000] { c.bench_function(&format!("LDT-XTS-AES-128/SHA-256/{num_keys} keys"), |b| { let mut state = build_bench_state::<_, sha2::Sha256>( ldt_factory::<16, XtsAes128, Swap, DefaultPadder>(), num_keys, 24, ); b.iter(|| black_box(state.scan())); }); c.bench_function(&format!("LDT-XTS-AES-128/SHA-256/XOR pad/{num_keys} keys"), |b| { let mut state = build_bench_state::<_, sha2::Sha256>( ldt_factory::< 16, XtsAes128, Swap, XorPadder<{ crypto_provider::aes::BLOCK_SIZE }>, >(), num_keys, 24, ); b.iter(|| black_box(state.scan())); }); c.bench_function(&format!("LDT-XTS-AES-256/SHA-256/{num_keys} keys",), |b| { let mut state = build_bench_state::<_, sha2::Sha256>( ldt_factory::<16, XtsAes256, Swap, DefaultPadder>(), num_keys, 24, ); b.iter(|| black_box(state.scan())); }); c.bench_function(&format!("AES-CTR-128/SHA-256/{num_keys} keys",), |b| { let mut state = build_bench_state::<_, sha2::Sha256>(AesCtrFactory {}, num_keys, 24); b.iter(|| black_box(state.scan())); }); c.bench_function(&format!("LDT-XTS-AES-128/BLAKE2b-512/{num_keys} keys",), |b| { let mut state = build_bench_state::<_, blake2::Blake2b512>( ldt_factory::<16, XtsAes128, Swap, DefaultPadder>(), num_keys, 24, ); b.iter(|| black_box(state.scan())); }); c.bench_function(&format!("LDT-XTS-AES-128/BLAKE2s-256/{num_keys} keys",), |b| { let mut state = build_bench_state::<_, blake2::Blake2s256>( ldt_factory::<16, XtsAes128, Swap, DefaultPadder>(), num_keys, 24, ); b.iter(|| black_box(state.scan())); }); } } criterion_group!(benches, ldt_scan); criterion_main!(benches); struct LdtBenchState { scenarios: Vec>, unfindable_ciphertext: Vec, decrypt_buf: Vec, } /// How much of the plaintext should be hashed for subsequent matching const MATCH_LEN: usize = 16; impl LdtBenchState { fn scan(&mut self) -> bool { let ciphertext = &self.unfindable_ciphertext; let mut hasher = D::new(); let mut hash_output = D::new_output(); self.scenarios.iter_mut().any(|scenario| { hasher.reset(); self.decrypt_buf.clear(); self.decrypt_buf.extend_from_slice(ciphertext); scenario.cipher.decrypt(&mut self.decrypt_buf[..]); // see if we decrypted to plaintext associated with this ldt / key hasher.update(&self.decrypt_buf[..MATCH_LEN]); hasher.finalize_and_reset(&mut hash_output); D::constant_time_compare(&scenario.plaintext_prefix_hash, &hash_output) }) } } fn build_bench_state( factory: F, keys: usize, plaintext_len: usize, ) -> LdtBenchState { let mut rng = ::CryptoRng::new(); let scenarios = (0..keys) .map(|_| random_ldt_scenario::(&factory, &mut rng, plaintext_len)) .collect::>(); LdtBenchState { scenarios, unfindable_ciphertext: random_vec::(&mut rng, plaintext_len), decrypt_buf: Vec::with_capacity(plaintext_len), } } struct ScanScenario { cipher: C, plaintext_prefix_hash: D::Output, } fn random_ldt_scenario( factory: &F, rng: &mut C::CryptoRng, plaintext_len: usize, ) -> ScanScenario { let cipher = factory.build_cipher::(rng); let plaintext = random_vec::(rng, plaintext_len); let mut hasher = D::new(); let mut plaintext_prefix_hash = D::new_output(); hasher.update(&plaintext[..MATCH_LEN]); hasher.finalize_and_reset(&mut plaintext_prefix_hash); ScanScenario { cipher, plaintext_prefix_hash } } fn random_vec(rng: &mut C::CryptoRng, len: usize) -> Vec { let mut bytes = Vec::::new(); bytes.extend((0..len).map(|_| rng.gen::())); bytes } trait ScanCipher { #[allow(dead_code)] fn encrypt(&mut self, buf: &mut [u8]); fn decrypt(&mut self, buf: &mut [u8]); } trait ScanCipherFactory { type Cipher: ScanCipher; fn build_cipher(&self, key_rng: &mut C::CryptoRng) -> Self::Cipher; } /// A wrapper that lets us avoid percolating the need to specify a bogus and type-confused padder /// for ciphers that don't use one. struct LdtScanCipher, M: Mix, P: Padder> { ldt_enc: LdtEncryptCipher, ldt_dec: LdtDecryptCipher, padder: P, } impl, M: Mix, P: Padder> ScanCipher for LdtScanCipher { fn encrypt(&mut self, buf: &mut [u8]) { self.ldt_enc.encrypt(buf, &self.padder).unwrap(); } fn decrypt(&mut self, buf: &mut [u8]) { self.ldt_dec.decrypt(buf, &self.padder).unwrap(); } } fn ldt_factory< const B: usize, T: TweakableBlockCipher, M: Mix, P: Padder + RandomPadder, >() -> LdtXtsAesFactory { LdtXtsAesFactory { padder_phantom: marker::PhantomData, key_phantom: marker::PhantomData, mix_phantom: marker::PhantomData, } } struct LdtXtsAesFactory< const B: usize, T: TweakableBlockCipher, M: Mix, P: Padder + RandomPadder, > { padder_phantom: marker::PhantomData

, key_phantom: marker::PhantomData, mix_phantom: marker::PhantomData, } impl ScanCipherFactory for LdtXtsAesFactory where T: TweakableBlockCipher, P: Padder + RandomPadder, M: Mix, { type Cipher = LdtScanCipher; fn build_cipher(&self, key_rng: &mut C::CryptoRng) -> Self::Cipher { let key: LdtKey = LdtKey::from_random::(key_rng); LdtScanCipher { ldt_enc: LdtEncryptCipher::new(&key), ldt_dec: LdtDecryptCipher::new(&key), padder: P::generate::(key_rng), } } } /// A helper trait for making padders from an RNG trait RandomPadder { fn generate(rng: &mut C::CryptoRng) -> Self; } impl RandomPadder for DefaultPadder { fn generate(_rng: &mut C::CryptoRng) -> Self { Self } } impl RandomPadder for XorPadder { fn generate(rng: &mut C::CryptoRng) -> Self { let mut salt = [0_u8; T]; rng.fill(&mut salt[..]); salt.into() } } type Aes128Ctr64LE = ctr::Ctr64LE; impl ScanCipher for Aes128Ctr64LE { fn encrypt(&mut self, buf: &mut [u8]) { self.seek(0); self.apply_keystream(buf) } fn decrypt(&mut self, buf: &mut [u8]) { self.seek(0); self.apply_keystream(buf) } } struct AesCtrFactory {} impl ScanCipherFactory for AesCtrFactory { type Cipher = Aes128Ctr64LE; fn build_cipher(&self, key_rng: &mut C::CryptoRng) -> Self::Cipher { let mut key = [0_u8; 16]; key_rng.fill(&mut key); let iv = [0_u8; 16]; Aes128Ctr64LE::new(&key.into(), &iv.into()) } } trait ScanDigest { type Output; fn new() -> Self; fn reset(&mut self); fn new_output() -> Self::Output; fn update(&mut self, data: &[u8]); fn finalize_and_reset(&mut self, out: &mut Self::Output); fn constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool; } impl ScanDigest for sha2::Sha256 { type Output = sha2::digest::generic_array::GenericArray; fn new() -> Self { ::new() } fn reset(&mut self) { ::reset(self) } fn new_output() -> Self::Output { Self::Output::default() } fn update(&mut self, data: &[u8]) { ::update(self, data); } fn finalize_and_reset(&mut self, out: &mut Self::Output) { self.finalize_into_reset(out); } fn constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool { a.ct_eq(b).into() } } impl ScanDigest for blake2::Blake2b512 { type Output = blake2::digest::generic_array::GenericArray; fn new() -> Self { ::new() } fn reset(&mut self) { ::reset(self) } fn new_output() -> Self::Output { Self::Output::default() } fn update(&mut self, data: &[u8]) { ::update(self, data) } fn finalize_and_reset(&mut self, out: &mut Self::Output) { self.finalize_into_reset(out) } fn constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool { a.ct_eq(b).into() } } impl ScanDigest for blake2::Blake2s256 { type Output = blake2::digest::generic_array::GenericArray; fn new() -> Self { ::new() } fn reset(&mut self) { ::reset(self) } fn new_output() -> Self::Output { Self::Output::default() } fn update(&mut self, data: &[u8]) { ::update(self, data) } fn finalize_and_reset(&mut self, out: &mut Self::Output) { self.finalize_into_reset(out) } fn constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool { a.ct_eq(b).into() } }