cipher_crypt/
porta.rs

1//! The Porta Cipher is a polyalphabetic substitution cipher. It was invented
2//! by Giovanni Battista della Porta, an Italian polymath, in 1563.
3//!
4//! To generate the keystream for encryption, a key is repeated as often as
5//! needed to match the number of (alphabetic) symbols in the plaintext message.
6//! Finally, the (alphabetic) symbols of the message are substituted using a
7//! substitution table. Since Porta is a reciprocal cipher, decryption works
8//! the same as encryption.
9//!
10//! This implementation uses the following substitution table:
11//! ```text
12//! Keys| a b c d e f g h i j k l m n o p q r s t u v w x y z
13//! ---------------------------------------------------------
14//! A,B | n o p q r s t u v w x y z a b c d e f g h i j k l m
15//! C,D | o p q r s t u v w x y z n m a b c d e f g h i j k l
16//! E,F | p q r s t u v w x y z n o l m a b c d e f g h i j k
17//! G,H | q r s t u v w x y z n o p k l m a b c d e f g h i j
18//! I,J | r s t u v w x y z n o p q j k l m a b c d e f g h i
19//! K,L | s t u v w x y z n o p q r i j k l m a b c d e f g h
20//! M,N | t u v w x y z n o p q r s h i j k l m a b c d e f g
21//! O,P | u v w x y z n o p q r s t g h i j k l m a b c d e f
22//! Q,R | v w x y z n o p q r s t u f g h i j k l m a b c d e
23//! S,T | w x y z n o p q r s t u v e f g h i j k l m a b c d
24//! U,V | x y z n o p q r s t u v w d e f g h i j k l m a b c
25//! W,X | y z n o p q r s t u v w x c d e f g h i j k l m a b
26//! ```
27//! For every key-message symbol pair `(k, m)`, the corresponding ciphertext
28//! symbol is determined by selecting the table row according to `k` and the
29//! column according to `m`.
30//!
31use crate::common::alphabet::{self, Alphabet};
32use crate::common::cipher::Cipher;
33use crate::common::keygen::cyclic_keystream;
34use crate::common::substitute;
35
36#[rustfmt::skip]
37const SUBSTITUTION_TABLE: [[usize; 26]; 13] = [
38    [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
39    [14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 13, 12,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11],
40    [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 13, 14, 11, 12,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
41    [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 13, 14, 15, 10, 11, 12,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
42    [17, 18, 19, 20, 21, 22, 23, 24, 25, 13, 14, 15, 16,  9, 10, 11, 12,  0,  1,  2,  3,  4,  5,  6,  7,  8],
43    [18, 19, 20, 21, 22, 23, 24, 25, 13, 14, 15, 16, 17,  8,  9, 10, 11, 12,  0,  1,  2,  3,  4,  5,  6,  7],
44    [19, 20, 21, 22, 23, 24, 25, 13, 14, 15, 16, 17, 18,  7,  8,  9, 10, 11, 12,  0,  1,  2,  3,  4,  5,  6],
45    [20, 21, 22, 23, 24, 25, 13, 14, 15, 16, 17, 18, 19,  6,  7,  8,  9, 10, 11, 12,  0,  1,  2,  3,  4,  5],
46    [21, 22, 23, 24, 25, 13, 14, 15, 16, 17, 18, 19, 20,  5,  6,  7,  8,  9, 10, 11, 12,  0,  1,  2,  3,  4],
47    [22, 23, 24, 25, 13, 14, 15, 16, 17, 18, 19, 20, 21,  4,  5,  6,  7,  8,  9, 10, 11, 12,  0,  1,  2,  3],
48    [23, 24, 25, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,  0,  1,  2],
49    [24, 25, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,  0,  1],
50    [25, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,  0],
51];
52
53/// A Porta cipher.
54///
55/// This struct is created by the `new()` method. See its documentation for more.
56pub struct Porta {
57    key: String,
58}
59
60impl Cipher for Porta {
61    type Key = String;
62    type Algorithm = Porta;
63
64    /// Initialize a Porta cipher given a specific key.
65    ///
66    /// # Panics
67    /// * The `key` is empty.
68    /// * The `key` contains a non-alphabetic symbol.
69    ///
70    fn new(key: String) -> Porta {
71        if key.is_empty() {
72            panic!("The key is empty.");
73        }
74        if !alphabet::STANDARD.is_valid(&key) {
75            panic!("The key contains a non-alphabetic symbol.");
76        }
77
78        Porta { key }
79    }
80
81    /// Encrypt a message using a Porta cipher.
82    ///
83    /// # Examples
84    /// Basic usage:
85    ///
86    /// ```
87    /// use cipher_crypt::{Cipher, Porta};
88    ///
89    /// let v = Porta::new("melon".into());
90    /// assert_eq!(v.encrypt("We ride at dawn!").unwrap(), "Dt mpwx pb xtdl!");
91    /// ```
92    ///
93    fn encrypt(&self, message: &str) -> Result<String, &'static str> {
94        Ok(substitute::key_substitution(
95            message,
96            &cyclic_keystream(&self.key, message),
97            |mi, ki| SUBSTITUTION_TABLE[ki / 2][mi],
98        ))
99    }
100
101    /// Decrypt a message using a Porta cipher.
102    ///
103    /// # Examples
104    /// Basic usage:
105    ///
106    /// ```
107    /// use cipher_crypt::{Cipher, Porta};
108    ///
109    /// let v = Porta::new(String::from("melon"));
110    /// assert_eq!(v.decrypt("Dt mpwx pb xtdl!").unwrap(), "We ride at dawn!");
111    /// ```
112    ///
113    fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
114        self.encrypt(ciphertext)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn encrypt() {
124        let message = "attackatdawn";
125        let porta = Porta::new("lemon".into());
126        assert_eq!(porta.encrypt(message).unwrap(), "seauvppaxtel");
127    }
128
129    #[test]
130    fn decrypt() {
131        let ciphertext = "seauvppaxtel";
132        let porta = Porta::new("lemon".into());
133        assert_eq!(porta.decrypt(ciphertext).unwrap(), "attackatdawn");
134    }
135
136    #[test]
137    fn mixed_case() {
138        let message = "Attack at Dawn!";
139        let porta = Porta::new("lemon".into());
140        let ciphertext = porta.encrypt(message).unwrap();
141        let decrypted = porta.decrypt(&ciphertext).unwrap();
142
143        assert_eq!(decrypted, message);
144    }
145
146    #[test]
147    fn with_utf8() {
148        let message = "Peace 🗡️ Freedom and Liberty!";
149        let porta = Porta::new("utfeightisfun".into());
150        let ciphertext = porta.encrypt(message).unwrap();
151        let decrypted = porta.decrypt(&ciphertext).unwrap();
152
153        assert_eq!(decrypted, message);
154    }
155
156    #[test]
157    fn valid_key() {
158        Porta::new("LeMon".into());
159    }
160
161    #[test]
162    #[should_panic]
163    fn key_with_symbols() {
164        Porta::new("!em@n".into());
165    }
166
167    #[test]
168    #[should_panic]
169    fn key_with_whitespace() {
170        Porta::new("wow this key is a real lemon".into());
171    }
172}