cryptography_breaker/algorithms/vigenere/
find_key_len.rs1use derive_builder::Builder;
6
7use crate::algorithms::vigenere::find_key_len::VigenereFindKeyLenResults::MultipleNearExpectedIoC;
8use crate::algorithms::{CipherError, index_of_coincidence, vigenere::Vigenere};
9pub enum VigenereFindKeyLenResults {
10 One(usize),
11 Multiple(Vec<usize>),
12 MultipleNearExpectedIoC(Vec<(usize, f64)>),
14}
15
16pub trait VigenereFindKeyLen {
17 fn run(&self, cipher_text: &str) -> Result<VigenereFindKeyLenResults, CipherError>;
18}
19
20#[derive(Builder)]
26pub struct VigenereFindKeyLenApprox<'a> {
27 expected_ioc: f64,
28 vigenere: &'a Vigenere<'a>,
29}
30
31impl<'a> VigenereFindKeyLen for VigenereFindKeyLenApprox<'a> {
32 fn run(&self, cipher_text: &str) -> Result<VigenereFindKeyLenResults, CipherError> {
33 let kp = self.expected_ioc;
34 let ko = index_of_coincidence(cipher_text);
35 let kr = 1.0 / self.vigenere.alphabet.len() as f64;
36
37 Ok(VigenereFindKeyLenResults::One(
38 ((kp - kr) / (ko - kr)).round() as usize,
39 ))
40 }
41}
42
43#[derive(Builder)]
44pub struct VigenereFindKeyLenTextSlices {
45 expected_ioc: f64,
46 max_results: Option<usize>,
47}
48
49impl VigenereFindKeyLen for VigenereFindKeyLenTextSlices {
50 fn run(&self, cipher_text: &str) -> Result<VigenereFindKeyLenResults, CipherError> {
51 let mut keys_len_closness_expected_ioc: Vec<(usize, f64)> = Vec::new();
53
54 let cleared_text: String = cipher_text.chars().filter(|c| !c.is_whitespace()).collect();
55 let mut results = text_slices_ioc(&cleared_text);
56
57 for (key_len, ioc) in &results {
58 let closeness = (self.expected_ioc - ioc).abs();
59 keys_len_closness_expected_ioc.push((*key_len, closeness));
60 }
61
62 keys_len_closness_expected_ioc.sort_by(|a, b| a.1.total_cmp(&b.1));
63
64 if let Some(max) = self.max_results {
66 results.truncate(max);
67 }
68
69 Ok(MultipleNearExpectedIoC(keys_len_closness_expected_ioc))
70 }
71}
72
73fn slice_text(text: &str, slices_number: usize) -> Vec<String> {
89 let chars: Vec<char> = text.chars().collect();
90
91 let mut slices = vec![String::new(); slices_number];
92
93 for (i, &c) in chars.iter().enumerate() {
94 slices[i % slices_number].push(c);
95 }
96
97 slices
98}
99
100fn text_slices_ioc(text: &str) -> Vec<(usize, f64)> {
106 let mut slices_ioc: Vec<(usize, f64)> = Vec::new();
107
108 for key_len in 1..=text.len() {
109 let slices = slice_text(text, key_len);
110 let mut sum = 0.0;
111
112 for slice in &slices {
113 sum += index_of_coincidence(slice);
114 }
115
116 let average = sum / key_len as f64;
117 slices_ioc.push((key_len, average));
118 }
119
120 slices_ioc
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::{
126 IoC,
127 algorithms::{
128 index_of_coincidence,
129 vigenere::find_key_len::{
130 VigenereFindKeyLen, VigenereFindKeyLenResults, VigenereFindKeyLenTextSlicesBuilder,
131 text_slices_ioc,
132 },
133 },
134 };
135
136 #[test]
138 #[should_panic(expected = "assertion `left == right` failed\n left: 8\n right: 24")]
139 fn test_vigenere_find_key_text_slices() {
140 let cipher_text = "VRJTVNQRLFTE VN XAFKL S UYIFCOHEPHE IYFZVOZMJT JPN EDVEX JPWV NMHCOYE FZXYNH ZVCY ETLJYMGMSE TNQSTLKCUI HCMNBZJSEYLD YNL AWKQQITXY T ONTMZP DZ DCAOZAIPGBDXIL IYJQQWANJT WBXPRZWLRKD TEDT DXQRYWLNP A KMDECNPGKD YRLZ H FCMUANH RHA OYKZNLWB SON DZJJTNQRLM ES QYXANZL RKDCTPTJ RKDZOARLAPD NOMRLRKD CZJFCY FN ZRZBWIOT";
141 let vigenere_find_key_len_text_slices = VigenereFindKeyLenTextSlicesBuilder::default()
142 .expected_ioc(IoC::Pl.value())
143 .max_results(None)
144 .build()
145 .unwrap();
146
147 let results = vigenere_find_key_len_text_slices.run(cipher_text).unwrap();
148
149 let mut first_key_len = 0;
150 if let VigenereFindKeyLenResults::MultipleNearExpectedIoC(keys_len) = results {
151 first_key_len = keys_len.first().unwrap().0;
152 }
158 assert_eq!(8, first_key_len)
159 }
160
161 mod test_slice_text {
162 use crate::algorithms::vigenere::find_key_len::slice_text;
163
164 #[test]
165 fn one_slice() {
166 let text = "AAAA";
167
168 assert_eq!(vec!["AAAA"], slice_text(text, 1))
169 }
170
171 #[test]
172 fn three_slices() {
173 let text = "CIPHERTEXT";
174
175 assert_eq!(vec!["CHTT", "IEE", "PRX"], slice_text(text, 3))
176 }
177
178 #[test]
179 fn five_slices() {
180 let text = "ABCDEABCDE";
181
182 assert_eq!(vec!["AA", "BB", "CC", "DD", "EE"], slice_text(text, 5))
183 }
184
185 #[test]
186 fn eight_slices() {
187 let text = "ABCDEFGHABC";
188
189 assert_eq!(
190 vec!["AA", "BB", "CC", "D", "E", "F", "G", "H"],
191 slice_text(text, 8)
192 )
193 }
194 }
195
196 #[test]
197 fn test_text_slices_ioc() {
198 let text = "ABCA";
199
200 let expected = vec![
201 (1, index_of_coincidence("ABCA")),
202 (
203 2,
204 (index_of_coincidence("AC") + index_of_coincidence("BA")) / 2.0,
205 ),
206 (
207 3,
208 (index_of_coincidence("AA")
209 + index_of_coincidence("B")
210 + index_of_coincidence("C"))
211 / 3.0,
212 ),
213 (
214 4,
215 (index_of_coincidence("A")
216 + index_of_coincidence("B")
217 + index_of_coincidence("C")
218 + index_of_coincidence("D"))
219 / 4.0,
220 ),
221 ];
222
223 assert_eq!(expected, text_slices_ioc(text))
224 }
225}