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 demonstration of LDT's PRP behavior.
16 //!
17 //! For each trial, between 16 and 31 bytes of random data are generated and encrypted. One random
18 //! bit of the ciphertext is flipped, then the ciphertext is decrypted. The percentage of flipped
19 //! bits vs the original plaintext is recorded, and the first n bytes are compared.
20 //!
21 //! The output shows how many times a change to the first n bytes wasn't detected, as well as a
22 //! histogram of how many bits were flipped in the entire plaintext.
23
24 #![allow(clippy::unwrap_used, clippy::indexing_slicing)]
25
26 use clap::Parser as _;
27 use crypto_provider::aes::BLOCK_SIZE;
28 use crypto_provider::{CryptoProvider, CryptoRng};
29 use crypto_provider_rustcrypto::RustCrypto;
30 use ldt::*;
31 use ldt_tbc::TweakableBlockCipher;
32 use rand::{distributions, Rng as _};
33
34 use rand_ext::*;
35 use xts_aes::{XtsAes128, XtsAes256};
36
main()37 fn main() {
38 let args = Args::parse();
39
40 run_trials(args)
41 }
42
run_trials(args: Args)43 fn run_trials(args: Args) {
44 let mut rng = seeded_rng();
45 let mut histo = (0..=100).map(|_| 0_u64).collect::<Vec<_>>();
46 let mut undetected_changes = 0_u64;
47 let mut cp_rng = <RustCrypto as CryptoProvider>::CryptoRng::new();
48 for _ in 0..args.trials {
49 let (percent, ok) =
50 if rng.gen() {
51 do_trial(
52 LdtEncryptCipher::<16, XtsAes128<RustCrypto>, Swap>::new(
53 &LdtKey::from_random::<RustCrypto>(&mut cp_rng),
54 ),
55 LdtDecryptCipher::<16, XtsAes128<RustCrypto>, Swap>::new(
56 &LdtKey::from_random::<RustCrypto>(&mut cp_rng),
57 ),
58 &mut rng,
59 DefaultPadder,
60 &args,
61 )
62 } else {
63 do_trial(
64 LdtEncryptCipher::<16, XtsAes256<RustCrypto>, Swap>::new(
65 &LdtKey::from_random::<RustCrypto>(&mut cp_rng),
66 ),
67 LdtDecryptCipher::<16, XtsAes256<RustCrypto>, Swap>::new(
68 &LdtKey::from_random::<RustCrypto>(&mut cp_rng),
69 ),
70 &mut rng,
71 DefaultPadder,
72 &args,
73 )
74 };
75
76 histo[percent] += 1;
77 if !ok {
78 undetected_changes += 1;
79 }
80 }
81
82 let sum: u64 = histo.iter().sum();
83 println!("Histogram of altered plaintext bits");
84 for (pct, count) in histo.iter().enumerate() {
85 // primitive horizontal bar graph
86 println!(
87 "{:3}%: {:8} {}",
88 pct,
89 count,
90 (0..((*count as f64) / (sum as f64) * 500_f64).round() as u32)
91 .map(|_| "*")
92 .collect::<String>()
93 );
94 }
95
96 println!(
97 "{} of {} trials ({:.4}%) failed detect changes to the first {} decrypted bytes",
98 undetected_changes,
99 args.trials,
100 undetected_changes as f64 / (args.trials as f64) * 100_f64,
101 args.check_leading_bytes
102 );
103 }
104
do_trial<const B: usize, T: TweakableBlockCipher<B>, P: Padder<B, T>, M: Mix, R: rand::Rng>( ldt_enc: LdtEncryptCipher<B, T, M>, ldt_dec: LdtDecryptCipher<B, T, M>, rng: &mut R, padder: P, args: &Args, ) -> (usize, bool)105 fn do_trial<const B: usize, T: TweakableBlockCipher<B>, P: Padder<B, T>, M: Mix, R: rand::Rng>(
106 ldt_enc: LdtEncryptCipher<B, T, M>,
107 ldt_dec: LdtDecryptCipher<B, T, M>,
108 rng: &mut R,
109 padder: P,
110 args: &Args,
111 ) -> (usize, bool) {
112 let plaintext_len_range = distributions::Uniform::new_inclusive(BLOCK_SIZE, BLOCK_SIZE * 2 - 1);
113 let len = rng.sample(plaintext_len_range);
114 let plaintext = random_vec_rc(rng, len);
115
116 let mut ciphertext = plaintext.clone();
117 ldt_enc.encrypt(&mut ciphertext, &padder).unwrap();
118
119 // flip a random bit
120 ciphertext[rng.gen_range(0..len)] ^= 1 << rng.gen_range(0..8);
121
122 ldt_dec.decrypt(&mut ciphertext, &padder).unwrap();
123 assert_ne!(plaintext, ciphertext);
124
125 let differing_bits: u32 = plaintext
126 .iter()
127 .zip(ciphertext.iter())
128 .map(|(plain_byte, mangled_byte)| (plain_byte ^ mangled_byte).count_ones())
129 .sum();
130
131 let percent = (differing_bits as f64) / (8_f64 * len as f64) * 100_f64;
132 let ok = plaintext[0..args.check_leading_bytes] != ciphertext[0..args.check_leading_bytes];
133
134 (percent.round() as usize, ok)
135 }
136
137 #[derive(clap::Parser, Debug)]
138 struct Args {
139 /// How many trials to run
140 #[clap(long, default_value_t = 1_000_000)]
141 trials: u64,
142 /// How many leading bytes to confirm are unchanged
143 #[clap(long, default_value_t = 16)]
144 check_leading_bytes: usize,
145 }
146