Skip to main content

cryptography_breaker/algorithms/
vigenere.rs

1//! Vigenere
2//!
3//! This module provide function like decrypt, encrypt and attack etc. It's provide core functionality
4
5pub mod attack;
6pub mod find_key_len;
7
8use std::collections::HashMap;
9
10use crate::{
11    algorithms::{
12        CipherError, modulus,
13        vigenere::attack::{
14            VigenereAttack, VigenereAttackResult, VigenereBrutforceAttackBuilder,
15            VigenereDictionaryAttackBuilder, VigenereVariationalAttackBuilder,
16        },
17    },
18    normalizer::{Normalizer, NormalizerResult},
19};
20
21use derive_builder::Builder;
22use freq_calc::{Alphabet, calctype::CalcType, tokenizer::Tokenizer};
23use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator};
24
25pub enum VigenereBreakMethods<'a, S: CalcType> {
26    Bruteforce {
27        key_len: usize,
28        reference_data: &'a HashMap<S::Key, f64>,
29        known_letters: Option<&'a [Option<char>]>,
30        penalty_score: Option<f64>,
31        max_results: Option<usize>,
32    },
33    Dictionary {
34        keys: Vec<String>,
35        reference_data: &'a HashMap<S::Key, f64>,
36        penalty_score: Option<f64>,
37        max_results: Option<usize>,
38    },
39    //Crib,
40    Variational {
41        penalty_score: Option<f64>,
42        reference_data: &'a HashMap<S::Key, f64>,
43        key_len: usize,
44    },
45    //Statistical,
46}
47
48pub enum VigenereFindKeyLengthMethods {
49    Approx,
50    TextSlicesIoC,
51}
52
53#[derive(Builder, Clone, Default)]
54#[builder(default)]
55pub struct Vigenere<'a> {
56    /// Alphabet used for decrypting and encrypting
57    #[builder(default = "Alphabet::ClassicEn.value()")]
58    alphabet: &'a [char],
59    /// How to normalize the output
60    normalizer: Normalizer,
61}
62impl<'a> Vigenere<'a> {
63    /// Decrypt and Encrypt are almost identical, the only difference is one symbol in equation.
64    /// This function deduplicate it by taking `op` which tells which operation to do
65    fn process_text<E>(&self, text: &str, key: &str, op: E) -> Result<String, CipherError>
66    where
67        E: Fn(isize, isize) -> isize,
68    {
69        if text.is_empty() {
70            return Err(CipherError::EmptyInputText);
71        }
72
73        if key.is_empty() {
74            return Err(CipherError::EmptyKey);
75        }
76
77        let key_chars: Vec<char> = key.chars().collect();
78
79        let mut index = 0;
80
81        text.chars()
82            // Map encrypted char to decrypted one
83            .map(|c| {
84                match self.normalizer.normalize(c, self.alphabet)? {
85                    NormalizerResult::Preserved(c) => Ok(c),
86                    NormalizerResult::Normalized { c, was_lowercase } => {
87                        let c_pos = self
88                            .alphabet
89                            .par_iter()
90                            .position_any(|&x| x == c)
91                            .ok_or(CipherError::InvalidInputChar(c))?;
92                        // Repeat key to be able to decrypt the char
93                        let k_char = key_chars[index % key_chars.len()];
94                        let k_pos = self
95                            .alphabet
96                            .par_iter()
97                            .position_any(|&x| x == k_char)
98                            .ok_or(CipherError::InvalidKey(key.to_string()))?;
99                        // Calculate plain char position by equation: p = (c - k) % 26
100                        let p_pos = modulus(
101                            op(c_pos as isize, k_pos as isize),
102                            self.alphabet.len() as isize,
103                        );
104
105                        index += 1;
106
107                        let mut processed_char = self.alphabet[p_pos];
108
109                        if was_lowercase {
110                            processed_char = processed_char
111                                .to_lowercase()
112                                .next()
113                                .ok_or(CipherError::InvalidInputChar(c))?;
114                        }
115
116                        Ok(processed_char)
117                    }
118                }
119            })
120            .collect()
121    }
122
123    /// Decrypt cipher text using Vigenère cipher
124    /// # Panics
125    /// This function will panic if you provide cipher_text or key that contains character not found in the alphabet
126    pub fn decrypt(&self, cipher_text: &str, key: &str) -> Result<String, CipherError> {
127        self.process_text(cipher_text, key, |a, b| a - b)
128    }
129
130    /// Encrypting cipher text using Vigenère cipher
131    /// # Panics
132    /// This function will panic if you provide cipher_text or key that contains character not found in the alphabet
133    pub fn encrypt(&self, plain_text: &str, key: &str) -> Result<String, CipherError> {
134        self.process_text(plain_text, key, |a, b| a + b)
135    }
136
137    #[allow(clippy::too_many_arguments)]
138    pub fn attack<S>(
139        &self,
140        method: VigenereBreakMethods<S>,
141        cipher_text: &str,
142    ) -> Result<VigenereAttackResult, CipherError>
143    where
144        S: CalcType + Tokenizer + Clone + Default,
145    {
146        match method {
147            // For LLM: i want to integrate vigenereattack for this for easy api
148            VigenereBreakMethods::Bruteforce {
149                key_len,
150                known_letters,
151                penalty_score,
152                reference_data,
153                max_results,
154            } => {
155                let attack = VigenereBrutforceAttackBuilder::<S>::default()
156                    .cipher_text(cipher_text)
157                    .vigenere(self)
158                    .key_len(key_len)
159                    .known_letters(known_letters)
160                    .reference_data(reference_data)
161                    .penalty_score(penalty_score)
162                    .max_results(max_results)
163                    .build()
164                    .unwrap();
165                attack.run()
166            }
167            VigenereBreakMethods::Dictionary {
168                keys,
169                reference_data,
170                penalty_score,
171                max_results,
172            } => {
173                let attack = VigenereDictionaryAttackBuilder::<S>::default()
174                    .cipher_text(cipher_text)
175                    .vigenere(self)
176                    .keys(keys)
177                    .reference_data(reference_data)
178                    .penalty_score(penalty_score)
179                    .max_results(max_results)
180                    .build()
181                    .unwrap();
182                attack.run()
183            }
184            VigenereBreakMethods::Variational {
185                penalty_score,
186                reference_data,
187                key_len,
188            } => {
189                let attack = VigenereVariationalAttackBuilder::<S>::default()
190                    .cipher_text(cipher_text)
191                    .key_len(key_len)
192                    .penalty_score(penalty_score)
193                    .reference_data(reference_data)
194                    .vigenere(self)
195                    .build()
196                    .unwrap();
197                attack.run()
198            }
199        }
200    }
201
202    pub fn find_key_len(method: VigenereFindKeyLengthMethods) {
203        match method {
204            VigenereFindKeyLengthMethods::Approx => todo!(),
205            VigenereFindKeyLengthMethods::TextSlicesIoC => todo!(),
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use freq_calc::Alphabet;
213    /*
214        static NORMALIZER_CONFIG: NormalizerConfig<PolishToAscii> = NormalizerConfig {
215            preserve_whitespaces: false,
216            case_strategy: ToUpper,
217            preserve_other_characters: false,
218            case_sensitive: false,
219            _transform: PolishToAscii,
220        };
221
222        static NORMALIZER_CONFIG_WORDS: NormalizerConfig<PolishToAscii> = NormalizerConfig {
223            preserve_whitespaces: true,
224            case_strategy: ToLower,
225            preserve_other_characters: false,
226            case_sensitive: false,
227            _transform: PolishToAscii,
228        };
229    */
230    use crate::algorithms::vigenere::{Vigenere, VigenereBuilder};
231    use crate::normalizer::{self, NormalizerBuilder};
232
233    // ======== Vigenere::decrypt() ========
234    #[test]
235    fn test_decrypt() {
236        let cipher_text = "CSKO GGZLCB XCHX";
237        let key = "KEY";
238        let normalizer = NormalizerBuilder::default()
239            .preserve_whitespaces(true)
240            .build()
241            .unwrap();
242        let vigenere = VigenereBuilder::default()
243            .normalizer(normalizer)
244            .build()
245            .unwrap();
246        let decrypted = Vigenere::decrypt(&vigenere, cipher_text, key).unwrap();
247
248        assert_eq!("SOME CIPHER TEXT", decrypted)
249    }
250
251    #[test]
252    fn test_decrypt_preserve_case() {
253        let cipher_text = "csko GGZLCB XCHX";
254        let key = "KEY";
255        let normalizer = NormalizerBuilder::default()
256            .case(normalizer::CaseStrategy::Preserve)
257            .preserve_whitespaces(true)
258            .build()
259            .unwrap();
260        let vigenere = VigenereBuilder::default()
261            .normalizer(normalizer)
262            .build()
263            .unwrap();
264        let decrypted = Vigenere::decrypt(&vigenere, cipher_text, key).unwrap();
265
266        assert_eq!("some CIPHER TEXT", decrypted)
267    }
268
269    // From TIN 2024–28 Kodowanie 2, Sem. 4
270    #[test]
271    fn test_decrypt_borowcowy() {
272        let cipher_text = "VRITVMQRLFTEBRY YAEOCYY CZIJTMFAYIEDKLPOBMLGLGJJPJKGGJOMKJPWVYYCSRZRCYNAYYNLBMAAJ EXCHXIPNTIBGXFZRXEEHS GYVSTXHSDUUIBMXAKAWKQPHTXYKMBIVUNZPDFMJZLBPCRGOCJEYMCYSNQOBQCATIKSJBHPYWLNTIBHOSD CXQQYWLNPDYYSNDEBRGASEKOBEAYF MAYOCAR RDIDRMUOYACCBRONKSJBHPYGBAXDVMJOJNLGAZJNTECXGRH YIPTQNAAGNPDFMLRLNTIBQIYQREDFMJPBOMPGKD";
273        let key = "LALECZKA";
274        let vigenere = VigenereBuilder::default()
275            .alphabet(Alphabet::Borowcowy.value())
276            .build()
277            .unwrap();
278        let decrypted = Vigenere::decrypt(&vigenere, cipher_text, key).unwrap();
279
280        assert_eq!(
281            "KRYPTOGRAFIA TO NAUKA O SZYFROWANIU INFORMACJI JEJ CELEM JEST OCHRONA DANYCH ORAZ UTAJNIENIE INFORMACJI WYKORZYSTUJE ONA ALGORYTMY I KLUCZE DO ZABEZPIECZENIA INFORMACJI SZYFROWANIE JEST STOSOWANE W INTERNECIE ORAZ W BANKACH GDY POKONASZ TEN SZYFROGRAM TO OZNACZA NIESTETY NIEPOPRAWNE DOBRANIE SZYFRU DO PROBLEMU",
282            decrypted
283        )
284    }
285
286    // ======== Vigenere::encrypt() ========
287    #[test]
288    fn test_encrypt() {
289        let plain_text = "SOME CIPHER TEXT";
290        let key = "KEY";
291        let normalizer = NormalizerBuilder::default()
292            .preserve_whitespaces(true)
293            .build()
294            .unwrap();
295        let vigenere = VigenereBuilder::default()
296            .normalizer(normalizer)
297            .build()
298            .unwrap();
299        let encrypted = Vigenere::encrypt(&vigenere, plain_text, key).unwrap();
300
301        assert_eq!("CSKO GGZLCB XCHX", encrypted)
302    }
303}