cipher_crypt/
autokey.rs

1//! An autokey cipher (also known as the autoclave cipher) is a cipher which incorporates the
2//! message (the plaintext) into the key.
3//!
4//! For example, say the message was `ATTACK AT DAWN` and the key was `CRYPT` then the calculated
5//! keystream would be `CRYPTA TT ACKA`. It was invented by Blaise de Vigenère in 1586, and is
6//! generally more secure than the Vigenere cipher.
7use crate::common::alphabet::Alphabet;
8use crate::common::cipher::Cipher;
9use crate::common::keygen::concatonated_keystream;
10use crate::common::{alphabet, substitute};
11
12/// An Autokey cipher.
13///
14/// This struct is created by the `new()` method. See its documentation for more.
15pub struct Autokey {
16    key: String,
17}
18
19impl Cipher for Autokey {
20    type Key = String;
21    type Algorithm = Autokey;
22
23    /// Initialise an Autokey cipher given a specific key.
24    ///
25    /// # Panics
26    /// * The `key` contains non-alphabetic symbols.
27    /// * The `key` is empty.
28    ///
29    fn new(key: String) -> Autokey {
30        if key.is_empty() {
31            panic!("The key must contain at least one character.");
32        } else if !alphabet::STANDARD.is_valid(&key) {
33            panic!("The key cannot contain non-alphabetic symbols.");
34        }
35
36        Autokey { key }
37    }
38
39    /// Encrypt a message using an Autokey cipher.
40    ///
41    /// # Examples
42    /// Basic usage:
43    ///
44    /// ```
45    /// use cipher_crypt::{Cipher, Autokey};
46    ///
47    /// let a = Autokey::new(String::from("fort"));
48    /// assert_eq!("Fhktcd 🗡 mhg otzx aade", a.encrypt("Attack 🗡 the east wall").unwrap());
49    /// ```
50    ///
51    fn encrypt(&self, message: &str) -> Result<String, &'static str> {
52        // Encryption of a letter in a message:
53        //         Ci = Ek(Mi) = (Mi + Ki) mod 26
54        // Where;  Mi = position within the alphabet of ith char in message
55        //         Ki = position within the alphabet of ith char in key
56        Ok(substitute::key_substitution(
57            message,
58            &concatonated_keystream(&self.key, message),
59            |mi, ki| alphabet::STANDARD.modulo((mi + ki) as isize),
60        ))
61    }
62
63    /// Decrypt a message using an Autokey cipher.
64    ///
65    /// # Examples
66    /// Basic usage:
67    ///
68    /// ```
69    /// use cipher_crypt::{Cipher, Autokey};
70    ///
71    /// let a = Autokey::new(String::from("fort"));;
72    /// assert_eq!("Attack 🗡 the east wall", a.decrypt("Fhktcd 🗡 mhg otzx aade").unwrap());
73    /// ```
74    ///
75    fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
76        //As each character of the ciphertext is decrypted, the un-encrypted char is appended
77        //to the base key 'keystream', so that it may be used to decrypt the latter part
78        //of the ciphertext
79        let mut plaintext = String::new();
80        let mut keystream: Vec<char> = self.key.clone().chars().collect();
81        let mut stream_idx: usize = 0;
82
83        for ct in ciphertext.chars() {
84            let ctpos = alphabet::STANDARD.find_position(ct);
85            match ctpos {
86                Some(ci) => {
87                    let decrypted_character: char;
88                    if let Some(kc) = keystream.get(stream_idx) {
89                        if let Some(ki) = alphabet::STANDARD.find_position(*kc) {
90                            //Calculate the index and retrieve the letter to substitute
91                            let si = alphabet::STANDARD.modulo(ci as isize - ki as isize);
92                            decrypted_character =
93                                alphabet::STANDARD.get_letter(si, ct.is_uppercase());
94                        } else {
95                            panic!("Keystream contains a non-alphabetic symbol.");
96                        }
97                    } else {
98                        panic!("Keystream is not large enough for full substitution of message.");
99                    }
100
101                    plaintext.push(decrypted_character);
102                    keystream.push(decrypted_character);
103                    stream_idx += 1;
104                }
105                None => plaintext.push(ct), //Push non-alphabetic chars 'as-is'
106            }
107        }
108
109        Ok(plaintext)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn with_utf8() {
119        let m = "Attack 🗡️ the east wall";
120        let a = Autokey::new(String::from("fort"));
121
122        assert_eq!(m, a.decrypt(&a.encrypt(m).unwrap()).unwrap());
123    }
124
125    #[test]
126    fn simple_encrypt_decrypt_test() {
127        let message = "defend the east wall of the castle";
128        let v = Autokey::new(String::from("fortification"));
129
130        let c_text = v.encrypt(message).unwrap();
131        let p_text = v.decrypt(&c_text).unwrap();
132
133        assert_eq!(message, p_text);
134    }
135
136    #[test]
137    fn decrypt_test() {
138        let ciphertext = "lxfopktmdcgn";
139        let v = Autokey::new(String::from("lemon"));
140        assert_eq!("attackatdawn", v.decrypt(ciphertext).unwrap());
141    }
142
143    #[test]
144    fn valid_key() {
145        Autokey::new(String::from("LeMon"));
146    }
147
148    #[test]
149    #[should_panic]
150    fn key_with_symbols() {
151        Autokey::new(String::from("!em@n"));
152    }
153
154    #[test]
155    #[should_panic]
156    fn key_with_whitespace() {
157        Autokey::new(String::from("wow this key is a real lemon"));
158    }
159}