cipher_crypt/
polybius.rs

1//! The Polybius square, also known as the Polybius checkerboard, is a device invented by the
2//! Ancient Greek historian and scholar Polybius, for fractionating plaintext characters so that
3//! they can be represented by a smaller set of symbols.
4//!
5use crate::common::alphabet::Alphabet;
6use crate::common::cipher::Cipher;
7use crate::common::{alphabet, keygen};
8use std::collections::HashMap;
9
10/// A Polybius square cipher.
11///
12/// This struct is created by the `new()` method. See its documentation for more.
13pub struct Polybius {
14    square: HashMap<String, char>,
15}
16
17impl Cipher for Polybius {
18    type Key = (String, [char; 6], [char; 6]);
19    type Algorithm = Polybius;
20
21    /// Initialise a Polybius square cipher.
22    ///
23    /// In this implementation each part of the `key` is used to initialise a 6x6 polybius square.
24    /// The `key` tuple maps to the following `(String, [char; 6], [char; 6]) = (phase,
25    /// column_ids, row_ids)`.
26    ///
27    /// Where ...
28    ///
29    /// * `phrase` is used to generate an alphanumeric keyed alphabet. It can contain characters
30    /// `a-z 0-9`.
31    /// * `column_ids` are unique identifiers used for each column of the polybius square. Valid
32    /// characters are alphabetic only (`a-z`).
33    /// * `row_ids` are unique identifiers used for each row of the polybius square. Valid
34    /// characters can be alphabetic only (`a-z`).
35    ///
36    /// # Panics
37    /// * If a non-alphanumeric symbol is part of the `key`.
38    /// * The `key` must have a length of 36.
39    /// * The `key` must contain each character of the alphanumeric alphabet `a-z`, `0-9`.
40    /// * The `column` and `row_ids` must contain alphabetic characters only.
41    /// * The `column` or `row_ids` contain repeated characters.
42    ///
43    /// # Example
44    /// Lets say the phrase was `or0an3ge` the column_ids were `['A','Z','C','D','E','F']`
45    /// and the row_ids were `['A','B','G','D','E','F']`. Then the polybius square would look like
46    /// ...
47    ///
48    /// ```md,no_run
49    /// __ A Z C D E F
50    /// A| o r 0 a n 3
51    /// B| g e b c d f
52    /// G| h i j k l m
53    /// D| p q s t u v
54    /// E| w x y z 1 2
55    /// F| 4 5 6 7 8 9
56    /// ```
57    /// Basic usage:
58    ///
59    /// ```
60    /// use cipher_crypt::{Cipher, Polybius};
61    ///
62    /// let p = Polybius::new((String::from("or0an3ge"), ['A','Z','C','D','E','F'],
63    ///     ['A','B','G','D','E','F']));;
64    ///
65    /// assert_eq!("EEAC AAazadaebabzdc adaebe EF ADdadagebzdc!",
66    ///    p.encrypt("10 Oranges and 2 Apples!").unwrap());
67    /// ```
68    ///
69    fn new(key: (String, [char; 6], [char; 6])) -> Polybius {
70        let alphabet_key = keygen::keyed_alphabet(&key.0, &alphabet::ALPHANUMERIC, false);
71        let square = keygen::polybius_square(&alphabet_key, &key.1, &key.2);
72
73        Polybius { square }
74    }
75
76    /// Encrypt a message using a Polybius square cipher.
77    ///
78    /// # Examples
79    /// Basic usage:
80    ///
81    /// ```
82    /// use cipher_crypt::{Cipher, Polybius};
83    ///
84    /// let p = Polybius::new((String::from("p0lyb1us"), ['A','Z','C','D','E','F'],
85    ///     ['A','B','G','D','E','F']));;
86    ///
87    /// assert_eq!("BCdfdfbcbdgf 🗡️ dfgcbf bfbcbzdf ezbcacac",
88    ///    p.encrypt("Attack 🗡️ the east wall").unwrap());
89    /// ```
90    ///
91    fn encrypt(&self, message: &str) -> Result<String, &'static str> {
92        Ok(message
93            .chars()
94            .map(|c| {
95                if let Some((key, _)) = self.square.iter().find(|e| e.1 == &c) {
96                    key.clone()
97                } else {
98                    c.to_string()
99                }
100            })
101            .collect())
102    }
103
104    /// Decrypt a message using a Polybius square cipher.
105    ///
106    /// # Examples
107    /// Basic usage:
108    ///
109    /// ```
110    /// use cipher_crypt::{Cipher, Polybius};
111    ///
112    /// let p = Polybius::new((String::from("p0lyb1us"), ['A','Z','C','D','E','F'],
113    ///     ['A','B','G','D','E','F']));;
114    ///
115    /// assert_eq!("Attack 🗡️ the east wall",
116    ///    p.decrypt("BCdfdfbcbdgf 🗡️ dfgcbf bfbcbzdf ezbcacac").unwrap());
117    /// ```
118    ///
119    fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
120        //We read the ciphertext two bytes at a time and transpose the original message using the
121        //polybius square
122        let mut message = String::new();
123        let mut buffer = String::new();
124
125        for c in ciphertext.chars() {
126            //Determine if the character could potentially be part of a 'polybius sequence' to
127            //be decrypted. Only standard alphabetic characters can be part of a valid sequence.
128            match alphabet::STANDARD.find_position(c) {
129                Some(_) => buffer.push(c),
130                None => message.push(c),
131            }
132
133            if buffer.len() == 2 {
134                match self.square.get(&buffer) {
135                    Some(&val) => message.push(val),
136                    None => return Err("Unknown sequence in the ciphertext."),
137                }
138
139                buffer.clear();
140            }
141        }
142
143        Ok(message)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn encrypt_message() {
153        //     A B C D E F
154        //  A| o r 0 a n g
155        //  B| e 1 b c d f
156        //  C| 2 h i j k 3
157        //  D| l m p 4 q s
158        //  E| 5 t u 6 v w
159        //  F| 7 x 8 y 9 z
160        let p = Polybius::new((
161            "or0ange1bcdf2hijk3lmp4qs5tu6vw7x8y9z".to_string(),
162            ['A', 'B', 'C', 'D', 'E', 'F'],
163            ['A', 'B', 'C', 'D', 'E', 'F'],
164        ));
165
166        assert_eq!(
167            "BBAC AAabadaeafbadf adaebe CA ADdcdcdabadf!",
168            p.encrypt("10 Oranges and 2 Apples!").unwrap()
169        );
170    }
171
172    #[test]
173    fn decrypt_message() {
174        let p = Polybius::new((
175            "or0ange1bcdf2hijk3lmp4qs5tu6vw7x8y9z".to_string(),
176            ['A', 'B', 'C', 'D', 'E', 'F'],
177            ['A', 'B', 'C', 'D', 'E', 'F'],
178        ));
179
180        assert_eq!(
181            "10 Oranges and 2 Apples!",
182            p.decrypt("BBAC AAabadaeafbadf adaebe CA ADdcdcdabadf!")
183                .unwrap()
184        );
185    }
186
187    #[test]
188    fn invalid_decrypt_sequence() {
189        let p = Polybius::new((
190            "or0ange1bcdf2hijk3lmp4qs5tu6vw7x8y9z".to_string(),
191            ['A', 'B', 'C', 'D', 'E', 'F'],
192            ['A', 'B', 'C', 'D', 'E', 'F'],
193        ));
194
195        //The sequnce 'AZ' is unknown to the polybius square
196        assert!(p
197            .decrypt("BBAC AZabadaeazbadf adaebe CA ADdcdcdabadf!")
198            .is_err());
199    }
200
201    #[test]
202    fn with_utf8() {
203        let m = "Attack 🗡️ the east wall";
204        let p = Polybius::new((
205            "or0ange1bcdf2hijk3lmp4qs5tu6vw7x8y9z".to_string(),
206            ['A', 'B', 'C', 'D', 'E', 'F'],
207            ['A', 'B', 'C', 'D', 'E', 'F'],
208        ));
209
210        assert_eq!(m, p.decrypt(&p.encrypt(m).unwrap()).unwrap());
211    }
212
213    #[test]
214    #[should_panic]
215    fn invalid_key_phrase() {
216        Polybius::new((
217            "F@IL".to_string(),
218            ['A', 'B', 'C', 'D', 'E', 'F'],
219            ['A', 'B', 'C', 'D', 'E', 'F'],
220        ));
221    }
222
223    #[test]
224    #[should_panic]
225    fn invalid_ids() {
226        Polybius::new((
227            "oranges".to_string(),
228            ['A', '!', 'C', 'D', 'E', 'F'],
229            ['A', 'B', '@', 'D', 'E', 'F'],
230        ));
231    }
232
233    #[test]
234    #[should_panic]
235    fn repeated_ids() {
236        Polybius::new((
237            "oranges".to_string(),
238            ['A', 'A', 'C', 'D', 'E', 'F'],
239            ['A', 'C', 'C', 'D', 'E', 'F'],
240        ));
241    }
242}