cipher_crypt/
adfgvx.rs

1//! The ADFGVX cipher was a field cipher used by the German Army on the Western Front during World War I.
2//!
3//! ADFGVX was an extension of an earlier cipher called ADFGX. It uses a polybius square and a
4//! columnar transposition cipher.
5//!
6use crate::columnar_transposition::ColumnarTransposition;
7use crate::common::cipher::Cipher;
8use crate::common::{alphabet, keygen};
9use crate::Polybius;
10use std::string::String;
11
12const ADFGVX_CHARS: [char; 6] = ['A', 'D', 'F', 'G', 'V', 'X'];
13
14/// This struct is created by the `new()` method. See its documentation for more.
15pub struct ADFGVX {
16    polybius_cipher: Polybius,
17    columnar_cipher: ColumnarTransposition,
18}
19
20impl Cipher for ADFGVX {
21    type Key = (String, String, Option<char>);
22    type Algorithm = ADFGVX;
23
24    /// Initialise a ADFGVX cipher.
25    ///
26    /// The `key` tuple maps to the following `(String, String, Option<char>) = (polybius_key,
27    /// columnar_key, null_char)`. Where ...
28    ///
29    /// * The `polybius_key` is used to init a polybius cipher. See it's documentation for more
30    /// information.
31    /// * The `columnar_key` is used to init a columnar transposition cipher. See it's
32    /// documentation for more information.
33    /// * The `null_char` is an optional character that will be used to pad uneven messages
34    /// during the columnar transposition stage. See the `columnar_transposition` documentation
35    /// for more information.
36    ///
37    /// # Panics
38    /// * If a non-alphanumeric symbol is part of the key.
39    ///
40    fn new(key: (String, String, Option<char>)) -> ADFGVX {
41        // Generate the keyed alphabet key for the polybius square
42        let p_key = keygen::keyed_alphabet(&key.0, &alphabet::ALPHANUMERIC, false);
43
44        ADFGVX {
45            polybius_cipher: Polybius::new((p_key, ADFGVX_CHARS, ADFGVX_CHARS)),
46            columnar_cipher: ColumnarTransposition::new((key.1, key.2)),
47        }
48    }
49
50    /// Encrypt a message using a ADFGVX cipher.
51    ///
52    /// # Examples
53    /// Basic usage:
54    ///
55    /// ```
56    /// use cipher_crypt::{Cipher, ADFGVX};
57    ///
58    /// let polybius_key = String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8");
59    /// let columnar_key = String::from("GERMAN");
60    /// let null_char = None;
61    ///
62    /// let a = ADFGVX::new((
63    ///     polybius_key,
64    ///     columnar_key,
65    ///     null_char
66    /// ));
67    ///
68    /// let cipher_text = concat!(
69    ///     "gfxffgxgDFAXDAVGDgxvadaaxxXFDDFGGGFdfaxdavgdVDAGFAXVVxfdd",
70    ///     "fgggfVVVAGFFAvvvagffaGXVADAAXXvdagfaxvvGFXFFGXG"
71    /// );
72    ///
73    /// assert_eq!(
74    ///     cipher_text,
75    ///     a.encrypt("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
76    ///         .unwrap()
77    /// );
78    /// ```
79    ///
80    fn encrypt(&self, message: &str) -> Result<String, &'static str> {
81        //Step 1: encrypt using polybius
82        let step_one = self.polybius_cipher.encrypt(message)?;
83        //Step 2: encrypt with columnar and return
84        self.columnar_cipher.encrypt(&step_one)
85    }
86
87    /// Decrypt a message using a ADFGVX cipher.
88    ///
89    /// # Examples
90    /// Basic usage:
91    ///
92    /// ```
93    /// use cipher_crypt::{Cipher, ADFGVX};
94    ///
95    /// let polybius_key = String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8");
96    /// let columnar_key = String::from("GERMAN");
97    /// let null_char = None;
98    ///
99    /// let a = ADFGVX::new((
100    ///     polybius_key,
101    ///     columnar_key,
102    ///     null_char
103    /// ));
104    ///
105    /// let cipher_text = concat!(
106    ///     "gfxffgxgDFAXDAVGD gxvadaaxxXFDDFGGGFdfaxdav",
107    ///     "gdVDAGFAXVVxfddfgggfVVVAGFFA vvvagffaGXVADAAXX vdagfaxvvGFXFFGXG "
108    /// );
109    ///
110    /// assert_eq!(
111    ///     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
112    ///      a.decrypt(cipher_text).unwrap()
113    /// );
114    /// ```
115    ///
116    fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
117        //Step 1: decrypt using columnar
118        let step_one = self.columnar_cipher.decrypt(ciphertext)?;
119        //Step 2: decrypt using polybius
120        self.polybius_cipher.decrypt(&step_one)
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn encrypt_simple() {
130        let a = ADFGVX::new((
131            String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8"),
132            String::from("GERMAN"),
133            None,
134        ));
135
136        let cipher_text = concat!(
137            "gfxffgxgDFAXDAVGDgxvadaaxxXFDDFGGGFdfaxdavgdVDAGFAX",
138            "VVxfddfgggfVVVAGFFAvvvagffaGXVADAAXXvdagfaxvvGFXFFGXG"
139        );
140        assert_eq!(
141            cipher_text,
142            a.encrypt("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
143                .unwrap()
144        );
145    }
146
147    #[test]
148    fn encrypt_with_space_padding() {
149        let a = ADFGVX::new((
150            String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8"),
151            String::from("GERMAN"),
152            Some(' '),
153        ));
154
155        // Note: this works as per crate version 0.11.0 - and leaves a trailing
156        //       ' ' in the ciphertext.
157        let cipher_text = concat!(
158            "gfxffgxgDFAXDAVGD gxvadaaxxXFDDFGGGFdfaxdavgdVDAGFAX",
159            "VVxfddfgggfVVVAGFFA vvvagffaGXVADAAXX vdagfaxvvGFXFFGXG "
160        );
161        assert_eq!(
162            cipher_text,
163            a.encrypt("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
164                .unwrap()
165        );
166    }
167
168    #[test]
169    fn decrypt_message() {
170        let a = ADFGVX::new((
171            String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8"),
172            String::from("GERMAN"),
173            None,
174        ));
175
176        let cipher_text = concat!(
177            "gfxffgxgDFAXDAVGDgxvadaaxxXFDDFGGGFdfaxdavgdVDAGFAX",
178            "VVxfddfgggfVVVAGFFAvvvagffaGXVADAAXXvdagfaxvvGFXFFGXG"
179        );
180        assert_eq!(
181            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
182            a.decrypt(cipher_text).unwrap()
183        );
184    }
185
186    #[test]
187    fn decrypt_with_space_padding() {
188        let a = ADFGVX::new((
189            String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8"),
190            String::from("GERMAN"),
191            Some(' '),
192        ));
193
194        // Note: this works as per crate version 0.11.0 - and leaves a trailing
195        //       ' ' in the ciphertext.
196        let cipher_text = concat!(
197            "gfxffgxgDFAXDAVGD gxvadaaxxXFDDFGGGFdfaxdavgdVDAGFAX",
198            "VVxfddfgggfVVVAGFFA vvvagffaGXVADAAXX vdagfaxvvGFXFFGXG "
199        );
200        assert_eq!(
201            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
202            a.decrypt(cipher_text).unwrap()
203        );
204    }
205
206    #[test]
207    fn simple() {
208        let a = ADFGVX::new((
209            String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8"),
210            String::from("VICTORY"),
211            None,
212        ));
213
214        let plain_text = concat!(
215            "We attack at dawn, not later when it is light, ",
216            "or at some strange time of the clock. Only at dawn."
217        );
218        assert_eq!(
219            a.decrypt(&a.encrypt(plain_text).unwrap()).unwrap(),
220            plain_text
221        );
222    }
223
224    #[test]
225    fn simple_with_padding() {
226        let a = ADFGVX::new((
227            String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8"),
228            String::from("VICTORY"),
229            Some('\u{0}'),
230        ));
231
232        let plain_text = concat!(
233            "We attack at dawn, not later when it is light, ",
234            "or at some strange time of the clock. Only at dawn."
235        );
236        assert_eq!(
237            a.decrypt(&a.encrypt(plain_text).unwrap()).unwrap(),
238            plain_text
239        );
240    }
241
242    #[test]
243    fn plaintext_with_padding() {
244        let a = ADFGVX::new((
245            String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8"),
246            String::from("VICTORY"),
247            Some(' '),
248        ));
249
250        let plain_text = "This will fail because of spaces.";
251        assert!(a.encrypt(plain_text).is_err());
252    }
253
254    #[test]
255    fn with_utf8() {
256        let plain_text = "Attack 🗡️ the east wall";
257        let a = ADFGVX::new((
258            String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8"),
259            String::from("GERMAN"),
260            None,
261        ));
262
263        assert_eq!(
264            plain_text,
265            a.decrypt(&a.encrypt(plain_text).unwrap()).unwrap()
266        );
267    }
268
269    #[test]
270    fn with_utf8_with_padding() {
271        let plain_text = "Attack 🗡️ the east wall";
272        let a = ADFGVX::new((
273            String::from("ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8"),
274            String::from("GERMAN"),
275            Some('\u{0}'),
276        ));
277
278        assert_eq!(
279            plain_text,
280            a.decrypt(&a.encrypt(plain_text).unwrap()).unwrap()
281        );
282    }
283
284    #[test]
285    #[should_panic]
286    fn invalid_key_phrase() {
287        ADFGVX::new((String::from("F@il"), String::from("GERMAN"), None));
288    }
289}