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}