cipher_crypt/
baconian.rs

1//! Bacon's cipher, or the Baconian cipher, hides a secret message in plain sight rather than
2//! generating ciphertext (steganography). It was devised by Sir Francis Bacon in 1605.
3//!
4//! Each character of the plaintext message is encoded as a 5-bit binary character.
5//! These characters are then "hidden" in a decoy message through the use of font variation.
6//! This cipher is very easy to crack once the method of hiding is known. As such, this implementation includes
7//! the option to set whether the substitution is use_distinct_alphabet for the whole alphabet,
8//! or whether it follows the classical method of treating 'I' and 'J', and 'U' and 'V' as
9//! interchangeable characters - as would have been the case in Bacon's time.
10//!
11//! If no concealing text is given and the boilerplate of "Lorem ipsum..." is used,
12//! a plaintext message of up to ~50 characters may be hidden.
13//!
14use crate::common::cipher::Cipher;
15use lipsum::lipsum;
16use std::collections::HashMap;
17use std::string::String;
18
19// The default code length
20const CODE_LEN: usize = 5;
21
22// Code mappings:
23//  * note: that str is preferred over char as it cannot be guaranteed that
24//     there will be a single codepoint for a given character.
25lazy_static! {
26    static ref CODE_MAP: HashMap<&'static str, &'static str> = hashmap! {
27        "A" => "AAAAA",
28        "B" => "AAAAB",
29        "C" => "AAABA",
30        "D" => "AAABB",
31        "E" => "AABAA",
32        "F" => "AABAB",
33        "G" => "AABBA",
34        "H" => "AABBB",
35        "I" => "ABAAA",
36        "J" => "ABAAB",
37        "K" => "ABABA",
38        "L" => "ABABB",
39        "M" => "ABBAA",
40        "N" => "ABBAB",
41        "O" => "ABBBA",
42        "P" => "ABBBB",
43        "Q" => "BAAAA",
44        "R" => "BAAAB",
45        "S" => "BAABA",
46        "T" => "BAABB",
47        "U" => "BABAA",
48        "V" => "BABAB",
49        "W" => "BABBA",
50        "X" => "BABBB",
51        "Y" => "BBAAA",
52        "Z" => "BBAAB"
53    };
54}
55
56// A mapping of alphabet to italic UTF-8 italic codes
57lazy_static! {
58    static ref ITALIC_CODES: HashMap<&'static str, char> = hashmap!{
59        // Using Mathematical Italic
60        "A" => '\u{1D434}',
61        "B" => '\u{1D435}',
62        "C" => '\u{1D436}',
63        "D" => '\u{1D437}',
64        "E" => '\u{1D438}',
65        "F" => '\u{1D439}',
66        "G" => '\u{1D43a}',
67        "H" => '\u{1D43b}',
68        "I" => '\u{1D43c}',
69        "J" => '\u{1D43d}',
70        "K" => '\u{1D43e}',
71        "L" => '\u{1D43f}',
72        "M" => '\u{1D440}',
73        "N" => '\u{1D441}',
74        "O" => '\u{1D442}',
75        "P" => '\u{1D443}',
76        "Q" => '\u{1D444}',
77        "R" => '\u{1D445}',
78        "S" => '\u{1D446}',
79        "T" => '\u{1D447}',
80        "U" => '\u{1D448}',
81        "V" => '\u{1D449}',
82        "W" => '\u{1D44a}',
83        "X" => '\u{1D44b}',
84        "Y" => '\u{1D44c}',
85        "Z" => '\u{1D44d}',
86        // Using Mathematical Sans-Serif Italic
87        "a" => '\u{1D622}',
88        "b" => '\u{1D623}',
89        "c" => '\u{1D624}',
90        "d" => '\u{1D625}',
91        "e" => '\u{1D626}',
92        "f" => '\u{1D627}',
93        "g" => '\u{1D628}',
94        "h" => '\u{1D629}',
95        "i" => '\u{1D62a}',
96        "j" => '\u{1D62b}',
97        "k" => '\u{1D62c}',
98        "l" => '\u{1D62d}',
99        "m" => '\u{1D62e}',
100        "n" => '\u{1D62f}',
101        "o" => '\u{1D630}',
102        "p" => '\u{1D631}',
103        "q" => '\u{1D632}',
104        "r" => '\u{1D633}',
105        "s" => '\u{1D634}',
106        "t" => '\u{1D635}',
107        "u" => '\u{1D636}',
108        "v" => '\u{1D637}',
109        "w" => '\u{1D638}',
110        "x" => '\u{1D639}',
111        "y" => '\u{1D63a}',
112        "z" => '\u{1D63b}'
113    };
114}
115
116/// Get the code for a given key (source character)
117fn get_code(use_distinct_alphabet: bool, key: &str) -> String {
118    let mut code = String::new();
119    // Need to handle 'I'/'J' and 'U'/'V'
120    //  for traditional usage.
121    let mut key_upper = key.to_uppercase();
122    if !use_distinct_alphabet {
123        match key_upper.as_str() {
124            "J" => key_upper = "I".to_string(),
125            "U" => key_upper = "V".to_string(),
126            _ => {}
127        }
128    }
129    if CODE_MAP.contains_key(key_upper.as_str()) {
130        code.push_str(CODE_MAP.get(key_upper.as_str()).unwrap());
131    }
132    code
133}
134
135/// Gets the key (the source character) for a given cipher text code
136fn get_key(code: &str) -> String {
137    let mut key = String::new();
138
139    for (_key, val) in CODE_MAP.iter() {
140        if val == &code {
141            key.push_str(_key);
142        }
143    }
144    key
145}
146
147/// This struct is created by the `new()` method. See its documentation for more.
148pub struct Baconian {
149    use_distinct_alphabet: bool,
150    decoy_text: String,
151}
152
153impl Cipher for Baconian {
154    type Key = (bool, Option<String>);
155    type Algorithm = Baconian;
156
157    /// Initialise a Baconian cipher
158    ///
159    /// The `key` tuple maps to the following: `(bool, Option<str>) = (use_distinct_alphabet, decoy_text)`.
160    /// Where ...
161    ///
162    /// * The encoding will be use_distinct_alphabet for all alphabetical characters, or classical
163    ///     where I, J, U and V are mapped to the same value pairs
164    /// * An optional decoy message that will will be used to hide the message -
165    ///     default is boilerplate "Lorem ipsum" text.
166    ///
167    fn new(key: (bool, Option<String>)) -> Baconian {
168        Baconian {
169            use_distinct_alphabet: key.0,
170            decoy_text: key.1.unwrap_or_else(|| lipsum(160)),
171        }
172    }
173
174    /// Encrypt a message using the Baconian cipher
175    ///
176    /// * The message to be encrypted can only be ~18% of the decoy_text as each character
177    ///     of message is encoded by 5 encoding characters `AAAAA`, `AAAAB`, etc.
178    /// * The italicised ciphertext is then hidden in a decoy text, where, for each 'B'
179    ///     in the ciphertext, the character is italicised in the decoy_text.
180    ///
181    /// # Examples
182    /// Basic usage:
183    ///
184    /// ```
185    /// use cipher_crypt::{Cipher, Baconian};
186    ///
187    /// let b = Baconian::new((false, None));;
188    /// let message = "Hello";
189    /// let cipher_text = "Lo𝘳𝘦𝘮 ip𝘴um d𝘰l𝘰𝘳 s𝘪t 𝘢𝘮e𝘵, 𝘤𝘰n";
190    ///
191    /// assert_eq!(cipher_text, b.encrypt(message).unwrap());
192    /// ```
193    fn encrypt(&self, message: &str) -> Result<String, &'static str> {
194        let num_non_alphas = self
195            .decoy_text
196            .chars()
197            .filter(|c| !c.is_alphabetic())
198            .count();
199
200        // Check whether the message fits in the decoy
201        // Note: that non-alphabetical characters will be skipped.
202        if (message.len() * CODE_LEN) > self.decoy_text.len() - num_non_alphas {
203            return Err("Message too long for supplied decoy text.");
204        }
205
206        // Iterate through the message encoding each char (ignoring non-alphabetical chars)
207        let secret: String = message
208            .chars()
209            .map(|c| get_code(self.use_distinct_alphabet, &c.to_string()))
210            .collect();
211
212        let mut num_alphas = 0;
213        let mut num_non_alphas = 0;
214        for c in self.decoy_text.chars() {
215            if num_alphas == secret.len() {
216                break;
217            }
218            if c.is_alphabetic() {
219                num_alphas += 1
220            } else {
221                num_non_alphas += 1
222            };
223        }
224
225        let decoy_slice: String = self
226            .decoy_text
227            .chars()
228            .take(num_alphas + num_non_alphas)
229            .collect();
230
231        // We now have an encoded message, `secret`, in which each character of of the
232        // original plaintext is now represented by a 5-bit binary character,
233        // "AAAAA", "ABABA" etc.
234        // We now overlay the encoded text onto the decoy slice, and
235        // where the binary 'B' is found the decoy slice char is swapped for an italic
236        let mut decoy_msg = String::new();
237        let mut secret_iter = secret.chars();
238        for c in decoy_slice.chars() {
239            if c.is_alphabetic() {
240                if let Some(sc) = secret_iter.next() {
241                    if sc == 'B' {
242                        // match the binary 'B' and swap for italic
243                        let italic = *ITALIC_CODES.get(c.to_string().as_str()).unwrap();
244                        decoy_msg.push(italic);
245                    } else {
246                        decoy_msg.push(c);
247                    }
248                }
249            } else {
250                decoy_msg.push(c);
251            }
252        }
253
254        Ok(decoy_msg)
255    }
256
257    /// Decrypt a message that was encrypted with the Baconian cipher
258    ///
259    /// # Examples
260    /// Basic usage:
261    ///
262    /// ```
263    /// use cipher_crypt::{Cipher, Baconian};
264    ///
265    /// let b = Baconian::new((false, None));;
266    /// let cipher_text = "Lo𝘳𝘦𝘮 ip𝘴um d𝘰l𝘰𝘳 s𝘪t 𝘢𝘮e𝘵, 𝘯𝘦 t";
267    ///
268    /// assert_eq!("HELLO", b.decrypt(cipher_text).unwrap());
269    /// ```
270    ///
271    fn decrypt(&self, message: &str) -> Result<String, &'static str> {
272        // The message is decoy text
273        // Iterate through swapping any alphabetical chars found in the ITALIC_CODES
274        // set to be 'B', else 'A', skip anything else.
275        let ciphertext: String = message
276            .chars()
277            .filter(|c| c.is_alphabetic())
278            .map(|c| {
279                if ITALIC_CODES.iter().any(|e| *e.1 == c) {
280                    'B'
281                } else {
282                    'A'
283                }
284            })
285            .collect();
286
287        let mut plaintext = String::new();
288        let mut code = String::new();
289        for c in ciphertext.chars() {
290            code.push(c);
291            if code.len() == CODE_LEN {
292                // If we have the right length code
293                plaintext += &get_key(&code);
294                code.clear();
295            }
296        }
297
298        Ok(plaintext)
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn encrypt_simple() {
308        let b = Baconian::new((false, None));
309        let message = "Hello";
310        let cipher_text = "Lo𝘳𝘦𝘮 ip𝘴um d𝘰l𝘰𝘳 s𝘪t 𝘢𝘮e𝘵, 𝘤𝘰n";
311        assert_eq!(cipher_text, b.encrypt(message).unwrap());
312    }
313    // Need to test that the traditional and use_distinct_alphabet codes give different results
314    #[test]
315    fn encrypt_trad_v_dist() {
316        let b_trad = Baconian::new((false, None));
317        let b_dist = Baconian::new((true, None));
318        let message = "I JADE YOU VERVENT UNICORN";
319
320        assert_ne!(
321            b_dist.encrypt(&message).unwrap(),
322            b_trad.encrypt(message).unwrap()
323        );
324    }
325
326    #[test]
327    fn encrypt_message_spaced() {
328        let decoy_text = String::from(
329            // The Life of Man, verse 1
330            "The world's a bubble; and the life of man less than a span. \
331             In his conception wretched; from the womb so to the tomb: \
332             Curst from the cradle, and brought up to years, with cares and fears. \
333             Who then to frail mortality shall trust, \
334             But limns the water, or but writes in dust. \
335             Yet, since with sorrow here we live oppress'd, what life is best? \
336             Courts are but only superficial schools to dandle fools: \
337             The rural parts are turn'd into a den of savage men: \
338             And where's a city from all vice so free, \
339             But may be term'd the worst of all the three?",
340        );
341        let b = Baconian::new((false, Some(decoy_text)));
342        let message = "Peace, Freedom 🗡️ and Liberty!";
343        let cipher_text = "T𝘩𝘦 𝘸𝘰rl𝘥\'s a bubble; an𝘥 the 𝘭ife o𝘧 m𝘢𝘯 les𝘴 th𝘢n a sp𝘢n. \
344                           In hi𝘴 𝘤o𝘯𝘤𝘦pt𝘪𝘰n wretche𝘥; 𝘧r𝘰m th𝘦 𝘸o𝘮b 𝘴𝘰 t𝘰 the tomb: \
345                           𝐶ur𝘴t f𝘳om t𝘩𝘦 cr𝘢𝘥𝘭𝘦, and";
346        assert_eq!(cipher_text, b.encrypt(message).unwrap());
347    }
348    // use_distinct_alphabet lexicon
349    #[test]
350    #[should_panic(expected = r#"Message too long for supplied decoy text."#)]
351    fn encrypt_decoy_too_short() {
352        let b = Baconian::new((false, None));
353        let message = "This is a long message that will be too long to encode using \
354                       the default decoy text. In order to have a long message encoded you need a \
355                       decoy text that is at least five times as long, plus the non-alphabeticals.";
356
357        b.encrypt(message).unwrap();
358    }
359
360    #[test]
361    fn encrypt_with_use_distinct_alphabet_codeset() {
362        let message = "Peace, Freedom 🗡️ and Liberty!";
363        let decoy_text = String::from(
364            // The Life of Man, verse 1
365            "The world's a bubble; and the life of man less than a span. \
366             In his conception wretched; from the womb so to the tomb: \
367             Curst from the cradle, and brought up to years, with cares and fears. \
368             Who then to frail mortality shall trust, \
369             But limns the water, or but writes in dust. \
370             Yet, since with sorrow here we live oppress'd, what life is best? \
371             Courts are but only superficial schools to dandle fools: \
372             The rural parts are turn'd into a den of savage men: \
373             And where's a city from all vice so free, \
374             But may be term'd the worst of all the three?",
375        );
376        let cipher_text = "T𝘩𝘦 𝘸𝘰rl𝘥's a bubble; an𝘥 the 𝘭ife o𝘧 m𝘢𝘯 les𝘴 th𝘢n a sp𝘢n. \
377                           In hi𝘴 𝘤o𝘯𝘤𝘦pt𝘪𝘰n wretche𝘥; 𝘧r𝘰m th𝘦 𝘸o𝘮b 𝘴𝘰 t𝘰 the tomb: \
378                           𝐶ur𝘴t f𝘳om t𝘩𝘦 cr𝘢𝘥𝘭𝘦, and";
379        let b = Baconian::new((true, Some(decoy_text)));
380        assert_eq!(cipher_text, b.encrypt(message).unwrap());
381    }
382
383    #[test]
384    fn decrypt_a_classic() {
385        let cipher_text = String::from("Let's c𝘰mp𝘳𝘰𝘮is𝘦. 𝐻old off th𝘦 at𝘵a𝘤k");
386        let message = "ATTACK";
387        let decoy_text = String::from("Let's compromise. Hold off the attack");
388        let b = Baconian::new((true, Some(decoy_text)));
389        assert_eq!(message, b.decrypt(&cipher_text).unwrap());
390    }
391
392    #[test]
393    fn decrypt_traditional() {
394        let cipher_text = String::from(
395            "T𝘩e wor𝘭d's a bubble; an𝘥 𝘵he 𝘭if𝘦 𝘰f man 𝘭𝘦𝘴s 𝘵h𝘢n 𝘢 𝘴p𝘢n. \
396             𝐼n h𝘪s c𝘰nce𝘱𝘵i𝘰n 𝘸re𝘵che𝘥; 𝘧r𝘰𝘮 th𝘦 𝘸𝘰m𝘣 s𝘰 t𝘰 𝘵h𝘦 t𝘰mb: \
397             Curs𝘵 fr𝘰𝘮 𝘵h𝘦 cra𝘥l𝘦, 𝘢n𝘥",
398        );
399        // Note: the substitution for 'I'/'J' and 'U'/'V'
400        let message = "IIADEYOVVERVENTVNICORN";
401        let decoy_text = String::from(
402            // The Life of Man, verse 1
403            "The world's a bubble; and the life of man less than a span. \
404             In his conception wretched; from the womb so to the tomb: \
405             Curst from the cradle, and brought up to years, with cares and fears. \
406             Who then to frail mortality shall trust, \
407             But limns the water, or but writes in dust. \
408             Yet, since with sorrow here we live oppress'd, what life is best? \
409             Courts are but only superficial schools to dandle fools: \
410             The rural parts are turn'd into a den of savage men: \
411             And where's a city from all vice so free, \
412             But may be term'd the worst of all the three?",
413        );
414        let b = Baconian::new((false, Some(decoy_text)));
415        assert_eq!(message, b.decrypt(&cipher_text).unwrap());
416    }
417}