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 //! A manual benchmark for more interactive parameter-twiddling.
16 
17 #![allow(clippy::unwrap_used, clippy::indexing_slicing)]
18 
19 use clap::Parser as _;
20 use crypto_provider_rustcrypto::RustCrypto;
21 use ldt::{LdtCipher, LdtDecryptCipher, LdtEncryptCipher, LdtKey, Mix, Swap, XorPadder};
22 
23 use crypto_provider::{CryptoProvider, CryptoRng};
24 use ldt_tbc::TweakableBlockCipher;
25 use rand::{distributions, seq::SliceRandom, Rng as _, SeedableRng as _};
26 use sha2::digest::{generic_array, Digest as _};
27 use std::time;
28 use subtle::ConstantTimeEq as _;
29 
30 use rand_ext::*;
31 use xts_aes::XtsAes128;
32 
main()33 fn main() {
34     let args = Args::parse();
35     let mut rng = <RustCrypto as CryptoProvider>::CryptoRng::new();
36 
37     // generate a suitable number of random keys
38     let scenarios = (0..args.keys)
39         .map(|_| {
40             random_ldt_scenario::<16, XtsAes128<RustCrypto>, Swap, RustCrypto>(&mut rng, args.len)
41         })
42         .collect::<Vec<_>>();
43 
44     let padder = XorPadder::from([0x42; crypto_provider::aes::BLOCK_SIZE]);
45 
46     let ciphertexts = scenarios
47         .iter()
48         .map(|s| {
49             let mut ciphertext = s.plaintext.clone();
50             s.ldt_enc.encrypt(&mut ciphertext[..], &padder).unwrap();
51             ciphertext
52         })
53         .collect::<Vec<_>>();
54 
55     let not_found_distrib = distributions::Uniform::from(0_f64..=100_f64);
56     let unfindable_ciphertext = random_vec::<RustCrypto>(&mut rng, args.len);
57 
58     let mut histogram = hdrhistogram::Histogram::<u64>::new(3).unwrap();
59     let mut buf = Vec::new();
60 
61     let mut hasher = sha2::Sha256::new();
62     let mut hash_output = generic_array::GenericArray::default();
63 
64     let mut rc_rng = rand::rngs::StdRng::from_entropy();
65     let found = (0..args.trials)
66         .map(|_| {
67             let ciphertext = if rc_rng.sample(not_found_distrib) <= args.not_found_pct as f64 {
68                 &unfindable_ciphertext
69             } else {
70                 ciphertexts.choose(&mut rc_rng).unwrap()
71             };
72 
73             let start = time::Instant::now();
74 
75             let found = scenarios.iter().any(|scenario| {
76                 hasher.reset();
77 
78                 buf.clear();
79                 buf.extend_from_slice(ciphertext.as_slice());
80                 scenario.ldt_dec.decrypt(&mut buf, &padder).unwrap();
81 
82                 hasher.update(&buf[..MATCH_LEN]);
83                 hasher.finalize_into_reset(&mut hash_output);
84 
85                 let arr_ref: &[u8; 32] = hash_output.as_ref();
86                 arr_ref.ct_eq(&scenario.plaintext_prefix_hash).into()
87             });
88 
89             histogram.record((start.elapsed().as_micros()) as u64).unwrap();
90 
91             found
92         })
93         .filter(|&found| found)
94         .count();
95 
96     println!(
97         "Found {} of {} ({}%)",
98         found,
99         args.trials,
100         (found as f64) / (args.trials as f64) * 100_f64
101     );
102 
103     println!(
104         "90%ile:    {}μs\n95%ile:    {}μs\n99%ile:    {}μs\n99.9%ile:  {}μs\n99.99%ile: {}μs\nMax:       {}μs",
105         histogram.value_at_quantile(0.90),
106         histogram.value_at_quantile(0.95),
107         histogram.value_at_quantile(0.99),
108         histogram.value_at_quantile(0.999),
109         histogram.value_at_quantile(0.9999),
110         histogram.max(),
111     );
112 }
113 
114 #[derive(clap::Parser, Debug)]
115 struct Args {
116     /// How many keys/plaintexts/ciphertexts to generate
117     #[clap(long, default_value_t = 1000)]
118     keys: u64,
119     /// How many trials to run
120     #[clap(long, default_value_t = 100_000)]
121     trials: u64,
122     /// Plaintext len
123     #[clap(long, default_value_t = 24)]
124     len: usize,
125     /// What percentage of decryptions should fail to find a match
126     #[clap(long, default_value_t = 50)]
127     not_found_pct: u8,
128 }
129 
130 /// How much of the plaintext should be hashed for subsequent matching
131 const MATCH_LEN: usize = 16;
132 
133 struct LdtScenario<const B: usize, T: TweakableBlockCipher<B>, M: Mix> {
134     ldt_enc: LdtEncryptCipher<B, T, M>,
135     ldt_dec: LdtDecryptCipher<B, T, M>,
136     plaintext: Vec<u8>,
137     plaintext_prefix_hash: [u8; 32],
138 }
139 
random_ldt_scenario<const B: usize, T: TweakableBlockCipher<B>, M: Mix, C: CryptoProvider>( rng: &mut C::CryptoRng, plaintext_len: usize, ) -> LdtScenario<B, T, M>140 fn random_ldt_scenario<const B: usize, T: TweakableBlockCipher<B>, M: Mix, C: CryptoProvider>(
141     rng: &mut C::CryptoRng,
142     plaintext_len: usize,
143 ) -> LdtScenario<B, T, M> {
144     let ldt_key: LdtKey<T::Key> = LdtKey::from_random::<C>(rng);
145     let ldt_enc = LdtEncryptCipher::new(&ldt_key);
146     let ldt_dec = LdtDecryptCipher::new(&ldt_key);
147     let plaintext = random_vec::<C>(rng, plaintext_len);
148 
149     let mut hasher = sha2::Sha256::new();
150     let mut plaintext_prefix_hash = generic_array::GenericArray::default();
151     hasher.update(&plaintext[..MATCH_LEN]);
152     hasher.finalize_into_reset(&mut plaintext_prefix_hash);
153 
154     LdtScenario { ldt_enc, ldt_dec, plaintext, plaintext_prefix_hash: plaintext_prefix_hash.into() }
155 }
156