1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #![allow(missing_docs, unused_results, clippy::indexing_slicing, clippy::unwrap_used)]
16 
17 use criterion::{black_box, criterion_group, criterion_main, Criterion};
18 use crypto_provider::{CryptoProvider, CryptoRng};
19 use crypto_provider_rustcrypto::RustCrypto;
20 use ctr::cipher::{KeyIvInit as _, StreamCipher as _, StreamCipherSeek as _};
21 use ldt::{
22     DefaultPadder, LdtCipher, LdtDecryptCipher, LdtEncryptCipher, LdtKey, Mix, Padder, Swap,
23     XorPadder,
24 };
25 use ldt_tbc::TweakableBlockCipher;
26 use sha2::Digest as _;
27 use std::marker;
28 use subtle::ConstantTimeEq as _;
29 use xts_aes::{XtsAes128, XtsAes256};
30 
ldt_scan(c: &mut Criterion)31 pub fn ldt_scan(c: &mut Criterion) {
32     for &num_keys in &[1_usize, 10, 1000] {
33         c.bench_function(&format!("LDT-XTS-AES-128/SHA-256/{num_keys} keys"), |b| {
34             let mut state = build_bench_state::<_, sha2::Sha256>(
35                 ldt_factory::<16, XtsAes128<RustCrypto>, Swap, DefaultPadder>(),
36                 num_keys,
37                 24,
38             );
39             b.iter(|| black_box(state.scan()));
40         });
41         c.bench_function(&format!("LDT-XTS-AES-128/SHA-256/XOR pad/{num_keys} keys"), |b| {
42             let mut state = build_bench_state::<_, sha2::Sha256>(
43                 ldt_factory::<
44                     16,
45                     XtsAes128<RustCrypto>,
46                     Swap,
47                     XorPadder<{ crypto_provider::aes::BLOCK_SIZE }>,
48                 >(),
49                 num_keys,
50                 24,
51             );
52             b.iter(|| black_box(state.scan()));
53         });
54         c.bench_function(&format!("LDT-XTS-AES-256/SHA-256/{num_keys} keys",), |b| {
55             let mut state = build_bench_state::<_, sha2::Sha256>(
56                 ldt_factory::<16, XtsAes256<RustCrypto>, Swap, DefaultPadder>(),
57                 num_keys,
58                 24,
59             );
60             b.iter(|| black_box(state.scan()));
61         });
62         c.bench_function(&format!("AES-CTR-128/SHA-256/{num_keys} keys",), |b| {
63             let mut state = build_bench_state::<_, sha2::Sha256>(AesCtrFactory {}, num_keys, 24);
64             b.iter(|| black_box(state.scan()));
65         });
66         c.bench_function(&format!("LDT-XTS-AES-128/BLAKE2b-512/{num_keys} keys",), |b| {
67             let mut state = build_bench_state::<_, blake2::Blake2b512>(
68                 ldt_factory::<16, XtsAes128<RustCrypto>, Swap, DefaultPadder>(),
69                 num_keys,
70                 24,
71             );
72             b.iter(|| black_box(state.scan()));
73         });
74         c.bench_function(&format!("LDT-XTS-AES-128/BLAKE2s-256/{num_keys} keys",), |b| {
75             let mut state = build_bench_state::<_, blake2::Blake2s256>(
76                 ldt_factory::<16, XtsAes128<RustCrypto>, Swap, DefaultPadder>(),
77                 num_keys,
78                 24,
79             );
80             b.iter(|| black_box(state.scan()));
81         });
82     }
83 }
84 
85 criterion_group!(benches, ldt_scan);
86 criterion_main!(benches);
87 
88 struct LdtBenchState<C: ScanCipher, D: ScanDigest> {
89     scenarios: Vec<ScanScenario<C, D>>,
90     unfindable_ciphertext: Vec<u8>,
91     decrypt_buf: Vec<u8>,
92 }
93 
94 /// How much of the plaintext should be hashed for subsequent matching
95 const MATCH_LEN: usize = 16;
96 
97 impl<C: ScanCipher, D: ScanDigest> LdtBenchState<C, D> {
scan(&mut self) -> bool98     fn scan(&mut self) -> bool {
99         let ciphertext = &self.unfindable_ciphertext;
100 
101         let mut hasher = D::new();
102         let mut hash_output = D::new_output();
103 
104         self.scenarios.iter_mut().any(|scenario| {
105             hasher.reset();
106             self.decrypt_buf.clear();
107             self.decrypt_buf.extend_from_slice(ciphertext);
108             scenario.cipher.decrypt(&mut self.decrypt_buf[..]);
109             // see if we decrypted to plaintext associated with this ldt / key
110             hasher.update(&self.decrypt_buf[..MATCH_LEN]);
111             hasher.finalize_and_reset(&mut hash_output);
112 
113             D::constant_time_compare(&scenario.plaintext_prefix_hash, &hash_output)
114         })
115     }
116 }
117 
build_bench_state<F: ScanCipherFactory, D: ScanDigest>( factory: F, keys: usize, plaintext_len: usize, ) -> LdtBenchState<F::Cipher, D>118 fn build_bench_state<F: ScanCipherFactory, D: ScanDigest>(
119     factory: F,
120     keys: usize,
121     plaintext_len: usize,
122 ) -> LdtBenchState<F::Cipher, D> {
123     let mut rng = <RustCrypto as CryptoProvider>::CryptoRng::new();
124 
125     let scenarios = (0..keys)
126         .map(|_| random_ldt_scenario::<RustCrypto, _, D>(&factory, &mut rng, plaintext_len))
127         .collect::<Vec<_>>();
128 
129     LdtBenchState {
130         scenarios,
131         unfindable_ciphertext: random_vec::<RustCrypto>(&mut rng, plaintext_len),
132         decrypt_buf: Vec::with_capacity(plaintext_len),
133     }
134 }
135 
136 struct ScanScenario<C: ScanCipher, D: ScanDigest> {
137     cipher: C,
138     plaintext_prefix_hash: D::Output,
139 }
140 
random_ldt_scenario<C: CryptoProvider, F: ScanCipherFactory, D: ScanDigest>( factory: &F, rng: &mut C::CryptoRng, plaintext_len: usize, ) -> ScanScenario<F::Cipher, D>141 fn random_ldt_scenario<C: CryptoProvider, F: ScanCipherFactory, D: ScanDigest>(
142     factory: &F,
143     rng: &mut C::CryptoRng,
144     plaintext_len: usize,
145 ) -> ScanScenario<F::Cipher, D> {
146     let cipher = factory.build_cipher::<C>(rng);
147     let plaintext = random_vec::<C>(rng, plaintext_len);
148     let mut hasher = D::new();
149     let mut plaintext_prefix_hash = D::new_output();
150     hasher.update(&plaintext[..MATCH_LEN]);
151     hasher.finalize_and_reset(&mut plaintext_prefix_hash);
152 
153     ScanScenario { cipher, plaintext_prefix_hash }
154 }
155 
random_vec<C: CryptoProvider>(rng: &mut C::CryptoRng, len: usize) -> Vec<u8>156 fn random_vec<C: CryptoProvider>(rng: &mut C::CryptoRng, len: usize) -> Vec<u8> {
157     let mut bytes = Vec::<u8>::new();
158     bytes.extend((0..len).map(|_| rng.gen::<u8>()));
159     bytes
160 }
161 
162 trait ScanCipher {
163     #[allow(dead_code)]
encrypt(&mut self, buf: &mut [u8])164     fn encrypt(&mut self, buf: &mut [u8]);
165 
decrypt(&mut self, buf: &mut [u8])166     fn decrypt(&mut self, buf: &mut [u8]);
167 }
168 
169 trait ScanCipherFactory {
170     type Cipher: ScanCipher;
171 
build_cipher<C: CryptoProvider>(&self, key_rng: &mut C::CryptoRng) -> Self::Cipher172     fn build_cipher<C: CryptoProvider>(&self, key_rng: &mut C::CryptoRng) -> Self::Cipher;
173 }
174 
175 /// A wrapper that lets us avoid percolating the need to specify a bogus and type-confused padder
176 /// for ciphers that don't use one.
177 struct LdtScanCipher<const B: usize, T: TweakableBlockCipher<B>, M: Mix, P: Padder<B, T>> {
178     ldt_enc: LdtEncryptCipher<B, T, M>,
179     ldt_dec: LdtDecryptCipher<B, T, M>,
180     padder: P,
181 }
182 
183 impl<const B: usize, T: TweakableBlockCipher<B>, M: Mix, P: Padder<B, T>> ScanCipher
184     for LdtScanCipher<B, T, M, P>
185 {
encrypt(&mut self, buf: &mut [u8])186     fn encrypt(&mut self, buf: &mut [u8]) {
187         self.ldt_enc.encrypt(buf, &self.padder).unwrap();
188     }
189 
decrypt(&mut self, buf: &mut [u8])190     fn decrypt(&mut self, buf: &mut [u8]) {
191         self.ldt_dec.decrypt(buf, &self.padder).unwrap();
192     }
193 }
194 
ldt_factory< const B: usize, T: TweakableBlockCipher<B>, M: Mix, P: Padder<B, T> + RandomPadder, >() -> LdtXtsAesFactory<B, T, M, P>195 fn ldt_factory<
196     const B: usize,
197     T: TweakableBlockCipher<B>,
198     M: Mix,
199     P: Padder<B, T> + RandomPadder,
200 >() -> LdtXtsAesFactory<B, T, M, P> {
201     LdtXtsAesFactory {
202         padder_phantom: marker::PhantomData,
203         key_phantom: marker::PhantomData,
204         mix_phantom: marker::PhantomData,
205     }
206 }
207 
208 struct LdtXtsAesFactory<
209     const B: usize,
210     T: TweakableBlockCipher<B>,
211     M: Mix,
212     P: Padder<B, T> + RandomPadder,
213 > {
214     padder_phantom: marker::PhantomData<P>,
215     key_phantom: marker::PhantomData<T>,
216     mix_phantom: marker::PhantomData<M>,
217 }
218 
219 impl<const B: usize, T, P, M> ScanCipherFactory for LdtXtsAesFactory<B, T, M, P>
220 where
221     T: TweakableBlockCipher<B>,
222     P: Padder<B, T> + RandomPadder,
223     M: Mix,
224 {
225     type Cipher = LdtScanCipher<B, T, M, P>;
226 
build_cipher<C: CryptoProvider>(&self, key_rng: &mut C::CryptoRng) -> Self::Cipher227     fn build_cipher<C: CryptoProvider>(&self, key_rng: &mut C::CryptoRng) -> Self::Cipher {
228         let key: LdtKey<T::Key> = LdtKey::from_random::<C>(key_rng);
229         LdtScanCipher {
230             ldt_enc: LdtEncryptCipher::new(&key),
231             ldt_dec: LdtDecryptCipher::new(&key),
232             padder: P::generate::<C>(key_rng),
233         }
234     }
235 }
236 
237 /// A helper trait for making padders from an RNG
238 trait RandomPadder {
generate<C: CryptoProvider>(rng: &mut C::CryptoRng) -> Self239     fn generate<C: CryptoProvider>(rng: &mut C::CryptoRng) -> Self;
240 }
241 
242 impl RandomPadder for DefaultPadder {
generate<C: CryptoProvider>(_rng: &mut C::CryptoRng) -> Self243     fn generate<C: CryptoProvider>(_rng: &mut C::CryptoRng) -> Self {
244         Self
245     }
246 }
247 
248 impl<const T: usize> RandomPadder for XorPadder<T> {
generate<C: CryptoProvider>(rng: &mut C::CryptoRng) -> Self249     fn generate<C: CryptoProvider>(rng: &mut C::CryptoRng) -> Self {
250         let mut salt = [0_u8; T];
251         rng.fill(&mut salt[..]);
252         salt.into()
253     }
254 }
255 
256 type Aes128Ctr64LE = ctr::Ctr64LE<aes::Aes128>;
257 
258 impl ScanCipher for Aes128Ctr64LE {
encrypt(&mut self, buf: &mut [u8])259     fn encrypt(&mut self, buf: &mut [u8]) {
260         self.seek(0);
261         self.apply_keystream(buf)
262     }
263 
decrypt(&mut self, buf: &mut [u8])264     fn decrypt(&mut self, buf: &mut [u8]) {
265         self.seek(0);
266         self.apply_keystream(buf)
267     }
268 }
269 
270 struct AesCtrFactory {}
271 
272 impl ScanCipherFactory for AesCtrFactory {
273     type Cipher = Aes128Ctr64LE;
274 
build_cipher<C: CryptoProvider>(&self, key_rng: &mut C::CryptoRng) -> Self::Cipher275     fn build_cipher<C: CryptoProvider>(&self, key_rng: &mut C::CryptoRng) -> Self::Cipher {
276         let mut key = [0_u8; 16];
277         key_rng.fill(&mut key);
278 
279         let iv = [0_u8; 16];
280 
281         Aes128Ctr64LE::new(&key.into(), &iv.into())
282     }
283 }
284 
285 trait ScanDigest {
286     type Output;
287 
new() -> Self288     fn new() -> Self;
289 
reset(&mut self)290     fn reset(&mut self);
291 
new_output() -> Self::Output292     fn new_output() -> Self::Output;
293 
update(&mut self, data: &[u8])294     fn update(&mut self, data: &[u8]);
295 
finalize_and_reset(&mut self, out: &mut Self::Output)296     fn finalize_and_reset(&mut self, out: &mut Self::Output);
297 
constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool298     fn constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool;
299 }
300 
301 impl ScanDigest for sha2::Sha256 {
302     type Output = sha2::digest::generic_array::GenericArray<u8, sha2::digest::consts::U32>;
303 
new() -> Self304     fn new() -> Self {
305         <Self as sha2::digest::Digest>::new()
306     }
307 
reset(&mut self)308     fn reset(&mut self) {
309         <Self as sha2::digest::Digest>::reset(self)
310     }
311 
new_output() -> Self::Output312     fn new_output() -> Self::Output {
313         Self::Output::default()
314     }
315 
update(&mut self, data: &[u8])316     fn update(&mut self, data: &[u8]) {
317         <Self as sha2::digest::Digest>::update(self, data);
318     }
319 
finalize_and_reset(&mut self, out: &mut Self::Output)320     fn finalize_and_reset(&mut self, out: &mut Self::Output) {
321         self.finalize_into_reset(out);
322     }
323 
constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool324     fn constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool {
325         a.ct_eq(b).into()
326     }
327 }
328 
329 impl ScanDigest for blake2::Blake2b512 {
330     type Output = blake2::digest::generic_array::GenericArray<u8, blake2::digest::consts::U64>;
331 
new() -> Self332     fn new() -> Self {
333         <Self as blake2::digest::Digest>::new()
334     }
335 
reset(&mut self)336     fn reset(&mut self) {
337         <Self as blake2::digest::Digest>::reset(self)
338     }
339 
new_output() -> Self::Output340     fn new_output() -> Self::Output {
341         Self::Output::default()
342     }
343 
update(&mut self, data: &[u8])344     fn update(&mut self, data: &[u8]) {
345         <Self as blake2::digest::Digest>::update(self, data)
346     }
347 
finalize_and_reset(&mut self, out: &mut Self::Output)348     fn finalize_and_reset(&mut self, out: &mut Self::Output) {
349         self.finalize_into_reset(out)
350     }
351 
constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool352     fn constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool {
353         a.ct_eq(b).into()
354     }
355 }
356 
357 impl ScanDigest for blake2::Blake2s256 {
358     type Output = blake2::digest::generic_array::GenericArray<u8, blake2::digest::consts::U32>;
359 
new() -> Self360     fn new() -> Self {
361         <Self as blake2::digest::Digest>::new()
362     }
363 
reset(&mut self)364     fn reset(&mut self) {
365         <Self as blake2::digest::Digest>::reset(self)
366     }
367 
new_output() -> Self::Output368     fn new_output() -> Self::Output {
369         Self::Output::default()
370     }
371 
update(&mut self, data: &[u8])372     fn update(&mut self, data: &[u8]) {
373         <Self as blake2::digest::Digest>::update(self, data)
374     }
375 
finalize_and_reset(&mut self, out: &mut Self::Output)376     fn finalize_and_reset(&mut self, out: &mut Self::Output) {
377         self.finalize_into_reset(out)
378     }
379 
constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool380     fn constant_time_compare(a: &Self::Output, b: &Self::Output) -> bool {
381         a.ct_eq(b).into()
382     }
383 }
384