cipher_crypt/
scytale.rs

1//! One of the oldest cryptography tools was a scytale, which was used to perform
2//! transposition encryption. It consisted of a cylinder with a strip of parchment (containing a
3//! message) wound around it.
4//!
5//! The ancient Greeks used this cipher to communicate during military campaigns. Sender and
6//! recipient each had a cylinder of exactly the same radius. The sender wound a narrow ribbon of
7//! parchment around their cylinder. Then they wrote on it lengthwise. After the ribbon is unwound,
8//! the writing could be read only by a person who had a cylinder of exactly the same
9//! circumference.
10//!
11//! Scytale encryption is only keyed by the number of letters that fit on each roll
12//! around the scytale. Therefore, it can be trivially cracked.
13//!
14use crate::common::cipher::Cipher;
15
16/// A Scytale cipher.
17///
18/// This struct is created by the `new()` method. See its documentation for more.
19pub struct Scytale {
20    height: usize,
21}
22
23impl Cipher for Scytale {
24    type Key = usize;
25    type Algorithm = Scytale;
26
27    /// Initialize a Scytale cipher with a specific cylinder height.
28    ///
29    /// # Panics
30    /// * The `key` is 0.
31    ///
32    fn new(key: usize) -> Scytale {
33        if key == 0 {
34            panic!("Invalid key, height cannot be zero.");
35        }
36
37        Scytale { height: key }
38    }
39
40    /// Encrypt a message using a Scytale cipher.
41    ///
42    /// Whilst all characters (including utf8) can be encrypted during the transposition process,
43    /// it is important to note that the space character is also treated as padding. As such,
44    /// whitespace characters at the end of a message are not preserved during the decryption
45    /// process.
46    ///
47    /// # Examples
48    /// Basic usage:
49    ///
50    /// ```
51    /// use cipher_crypt::{Cipher, Scytale};
52    ///
53    /// let s = Scytale::new(6);
54    /// assert_eq!("Pegr lefoporaryr !", s.encrypt("Prepare for glory!").unwrap());
55    /// ```
56    ///
57    fn encrypt(&self, message: &str) -> Result<String, &'static str> {
58        // In both these cases the message is not altered
59        if self.height >= message.chars().count() || self.height == 1 {
60            return Ok(message.to_string());
61        }
62
63        // Create the smallest table that fits the message
64        let width = (message.chars().count() as f64 / self.height as f64).ceil() as usize;
65        let mut table = vec![vec![' '; width]; self.height];
66
67        // Iterate over message and insert into the table, along rows
68        for (pos, element) in message.chars().enumerate() {
69            let col = pos % self.height;
70            let row = pos / self.height;
71
72            table[col][row] = element;
73        }
74
75        // Construct the ciphertext out of each row
76        // Trim off any trailing whitespace added
77        Ok(table
78            .iter()
79            .flatten()
80            .collect::<String>()
81            .trim_end()
82            .to_string())
83    }
84
85    /// Decrypt a message using a Scytale cipher.
86    ///
87    /// # Examples
88    /// Basic usage:
89    ///
90    /// ```
91    /// use cipher_crypt::{Cipher, Scytale};
92    ///
93    /// let ct = Scytale::new(6);
94    /// assert_eq!("Prepare for glory!", ct.decrypt("Pegr lefoporaryr !").unwrap());
95    /// ```
96    ///
97    fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
98        // In both these cases the ciphertext has not been altered
99        if self.height >= ciphertext.chars().count() || self.height == 1 {
100            return Ok(ciphertext.to_string());
101        }
102
103        // Create the smallest table that fits the ciphertext
104        let width = (ciphertext.chars().count() as f64 / self.height as f64).ceil() as usize;
105        let mut table = vec![vec![' '; width]; self.height];
106
107        // Iterate over ciphertext and insert into the table, along columns
108        for (pos, element) in ciphertext.chars().enumerate() {
109            let col = pos / width;
110            let row = pos % width;
111
112            table[col][row] = element;
113        }
114
115        // Traverse each column and construct the plaintext
116        let mut plaintext = String::new();
117        while table.iter().filter(|v| !v.is_empty()).count() > 0 {
118            // Continously pop from the top of each column until all are empty
119            for column in table.iter_mut() {
120                plaintext.push(column.remove(0));
121            }
122        }
123
124        //Make sure to strip any padding characters
125        Ok(plaintext.trim_end().to_string())
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn simple_encrypt() {
135        let s = Scytale::new(6);
136        assert_eq!("aatttdaacwkn", s.encrypt("attackatdawn").unwrap());
137    }
138
139    #[test]
140    fn simple_decrypt() {
141        let s = Scytale::new(6);
142        assert_eq!("attackatdawn", s.decrypt("aatttdaacwkn").unwrap());
143    }
144
145    #[test]
146    fn padding_required() {
147        let s = Scytale::new(5);
148        let m = "attackatdawn";
149        assert_eq!(m, s.decrypt(&s.encrypt(m).unwrap()).unwrap());
150    }
151
152    #[test]
153    #[should_panic]
154    fn invalid_height() {
155        Scytale::new(0);
156    }
157
158    #[test]
159    fn with_utf8() {
160        let s = Scytale::new(5);
161        let m = "Attack 🗡️ at once.";
162        assert_eq!(m, s.decrypt(&s.encrypt(m).unwrap()).unwrap());
163    }
164
165    #[test]
166    fn with_spaces() {
167        //Spaces at the end of a message are not preserved
168        let s = Scytale::new(5);
169        let m = "Attack At Dawn comrades!  ";
170        assert_eq!(
171            "Attack At Dawn comrades!",
172            s.decrypt(&s.encrypt(m).unwrap()).unwrap()
173        );
174    }
175
176    #[test]
177    fn longer_height() {
178        let s = Scytale::new(20);
179        let m = "attackatdawn";
180        assert_eq!(m, s.decrypt(&s.encrypt(m).unwrap()).unwrap());
181    }
182
183    #[test]
184    fn longer_msg() {
185        let s = Scytale::new(7);
186        let m = concat!(
187            "We attack at dawn, not later when it is light, ",
188            "or at some strange time of the clock. Only at dawn. ",
189            "Why do we like to attack at dawn, actually, I don\'t ",
190            "get it. I hate getting up that early, it puts me in ",
191            "a bad mood. Can\'t we do it a bit later, say nine-thirty?"
192        );
193        assert_eq!(m, s.decrypt(&s.encrypt(m).unwrap()).unwrap());
194    }
195}