| // 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<RustCrypto>, 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<RustCrypto>, | 
 |                     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<RustCrypto>, 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<RustCrypto>, 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<RustCrypto>, Swap, DefaultPadder>(), | 
 |                 num_keys, | 
 |                 24, | 
 |             ); | 
 |             b.iter(|| black_box(state.scan())); | 
 |         }); | 
 |     } | 
 | } | 
 |  | 
 | criterion_group!(benches, ldt_scan); | 
 | criterion_main!(benches); | 
 |  | 
 | struct LdtBenchState<C: ScanCipher, D: ScanDigest> { | 
 |     scenarios: Vec<ScanScenario<C, D>>, | 
 |     unfindable_ciphertext: Vec<u8>, | 
 |     decrypt_buf: Vec<u8>, | 
 | } | 
 |  | 
 | /// How much of the plaintext should be hashed for subsequent matching | 
 | const MATCH_LEN: usize = 16; | 
 |  | 
 | impl<C: ScanCipher, D: ScanDigest> LdtBenchState<C, D> { | 
 |     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<F: ScanCipherFactory, D: ScanDigest>( | 
 |     factory: F, | 
 |     keys: usize, | 
 |     plaintext_len: usize, | 
 | ) -> LdtBenchState<F::Cipher, D> { | 
 |     let mut rng = <RustCrypto as CryptoProvider>::CryptoRng::new(); | 
 |  | 
 |     let scenarios = (0..keys) | 
 |         .map(|_| random_ldt_scenario::<RustCrypto, _, D>(&factory, &mut rng, plaintext_len)) | 
 |         .collect::<Vec<_>>(); | 
 |  | 
 |     LdtBenchState { | 
 |         scenarios, | 
 |         unfindable_ciphertext: random_vec::<RustCrypto>(&mut rng, plaintext_len), | 
 |         decrypt_buf: Vec::with_capacity(plaintext_len), | 
 |     } | 
 | } | 
 |  | 
 | struct ScanScenario<C: ScanCipher, D: ScanDigest> { | 
 |     cipher: C, | 
 |     plaintext_prefix_hash: D::Output, | 
 | } | 
 |  | 
 | fn random_ldt_scenario<C: CryptoProvider, F: ScanCipherFactory, D: ScanDigest>( | 
 |     factory: &F, | 
 |     rng: &mut C::CryptoRng, | 
 |     plaintext_len: usize, | 
 | ) -> ScanScenario<F::Cipher, D> { | 
 |     let cipher = factory.build_cipher::<C>(rng); | 
 |     let plaintext = random_vec::<C>(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<C: CryptoProvider>(rng: &mut C::CryptoRng, len: usize) -> Vec<u8> { | 
 |     let mut bytes = Vec::<u8>::new(); | 
 |     bytes.extend((0..len).map(|_| rng.gen::<u8>())); | 
 |     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<C: CryptoProvider>(&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<const B: usize, T: TweakableBlockCipher<B>, M: Mix, P: Padder<B, T>> { | 
 |     ldt_enc: LdtEncryptCipher<B, T, M>, | 
 |     ldt_dec: LdtDecryptCipher<B, T, M>, | 
 |     padder: P, | 
 | } | 
 |  | 
 | impl<const B: usize, T: TweakableBlockCipher<B>, M: Mix, P: Padder<B, T>> ScanCipher | 
 |     for LdtScanCipher<B, T, M, P> | 
 | { | 
 |     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<B>, | 
 |     M: Mix, | 
 |     P: Padder<B, T> + RandomPadder, | 
 | >() -> LdtXtsAesFactory<B, T, M, P> { | 
 |     LdtXtsAesFactory { | 
 |         padder_phantom: marker::PhantomData, | 
 |         key_phantom: marker::PhantomData, | 
 |         mix_phantom: marker::PhantomData, | 
 |     } | 
 | } | 
 |  | 
 | struct LdtXtsAesFactory< | 
 |     const B: usize, | 
 |     T: TweakableBlockCipher<B>, | 
 |     M: Mix, | 
 |     P: Padder<B, T> + RandomPadder, | 
 | > { | 
 |     padder_phantom: marker::PhantomData<P>, | 
 |     key_phantom: marker::PhantomData<T>, | 
 |     mix_phantom: marker::PhantomData<M>, | 
 | } | 
 |  | 
 | impl<const B: usize, T, P, M> ScanCipherFactory for LdtXtsAesFactory<B, T, M, P> | 
 | where | 
 |     T: TweakableBlockCipher<B>, | 
 |     P: Padder<B, T> + RandomPadder, | 
 |     M: Mix, | 
 | { | 
 |     type Cipher = LdtScanCipher<B, T, M, P>; | 
 |  | 
 |     fn build_cipher<C: CryptoProvider>(&self, key_rng: &mut C::CryptoRng) -> Self::Cipher { | 
 |         let key: LdtKey<T::Key> = LdtKey::from_random::<C>(key_rng); | 
 |         LdtScanCipher { | 
 |             ldt_enc: LdtEncryptCipher::new(&key), | 
 |             ldt_dec: LdtDecryptCipher::new(&key), | 
 |             padder: P::generate::<C>(key_rng), | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | /// A helper trait for making padders from an RNG | 
 | trait RandomPadder { | 
 |     fn generate<C: CryptoProvider>(rng: &mut C::CryptoRng) -> Self; | 
 | } | 
 |  | 
 | impl RandomPadder for DefaultPadder { | 
 |     fn generate<C: CryptoProvider>(_rng: &mut C::CryptoRng) -> Self { | 
 |         Self | 
 |     } | 
 | } | 
 |  | 
 | impl<const T: usize> RandomPadder for XorPadder<T> { | 
 |     fn generate<C: CryptoProvider>(rng: &mut C::CryptoRng) -> Self { | 
 |         let mut salt = [0_u8; T]; | 
 |         rng.fill(&mut salt[..]); | 
 |         salt.into() | 
 |     } | 
 | } | 
 |  | 
 | type Aes128Ctr64LE = ctr::Ctr64LE<aes::Aes128>; | 
 |  | 
 | 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<C: CryptoProvider>(&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<u8, sha2::digest::consts::U32>; | 
 |  | 
 |     fn new() -> Self { | 
 |         <Self as sha2::digest::Digest>::new() | 
 |     } | 
 |  | 
 |     fn reset(&mut self) { | 
 |         <Self as sha2::digest::Digest>::reset(self) | 
 |     } | 
 |  | 
 |     fn new_output() -> Self::Output { | 
 |         Self::Output::default() | 
 |     } | 
 |  | 
 |     fn update(&mut self, data: &[u8]) { | 
 |         <Self as sha2::digest::Digest>::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<u8, blake2::digest::consts::U64>; | 
 |  | 
 |     fn new() -> Self { | 
 |         <Self as blake2::digest::Digest>::new() | 
 |     } | 
 |  | 
 |     fn reset(&mut self) { | 
 |         <Self as blake2::digest::Digest>::reset(self) | 
 |     } | 
 |  | 
 |     fn new_output() -> Self::Output { | 
 |         Self::Output::default() | 
 |     } | 
 |  | 
 |     fn update(&mut self, data: &[u8]) { | 
 |         <Self as blake2::digest::Digest>::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<u8, blake2::digest::consts::U32>; | 
 |  | 
 |     fn new() -> Self { | 
 |         <Self as blake2::digest::Digest>::new() | 
 |     } | 
 |  | 
 |     fn reset(&mut self) { | 
 |         <Self as blake2::digest::Digest>::reset(self) | 
 |     } | 
 |  | 
 |     fn new_output() -> Self::Output { | 
 |         Self::Output::default() | 
 |     } | 
 |  | 
 |     fn update(&mut self, data: &[u8]) { | 
 |         <Self as blake2::digest::Digest>::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() | 
 |     } | 
 | } |