cipher_crypt/railfence.rs
1//! The Railfence Cipher is a transposition cipher. It has a very low keyspace and is therefore
2//! incredibly insecure.
3//!
4//! This implementation currently transposes all input characters including whitespace and
5//! punctuation.
6
7/// A Railfence cipher.
8///
9/// This struct is created by the `new()` method. See its documentation for more.
10use crate::common::cipher::Cipher;
11
12pub struct Railfence {
13 rails: usize,
14}
15
16impl Cipher for Railfence {
17 type Key = usize;
18 type Algorithm = Railfence;
19
20 /// Initialise a Railfence cipher given a specific key (number of rails).
21 ///
22 /// # Panics
23 /// * The `key` is 0.
24 ///
25 fn new(key: usize) -> Railfence {
26 if key == 0 {
27 panic!("The key is 0.");
28 }
29
30 Railfence { rails: key }
31 }
32
33 /// Encrypt a message using a Railfence cipher.
34 ///
35 /// # Examples
36 /// Basic usage:
37 ///
38 /// ```
39 /// use cipher_crypt::{Cipher, Railfence};
40 ///
41 /// let r = Railfence::new(3);
42 /// assert_eq!("Src s!ue-ertmsaepseeg", r.encrypt("Super-secret message!").unwrap());
43 /// ```
44 ///
45 fn encrypt(&self, message: &str) -> Result<String, &'static str> {
46 // Encryption process:
47 // First a table is created with a height given by the key and a length
48 // given by the message length.
49 // e.g.
50 // For a key of 3 and the message "Hello, World!" of length 13:
51 // .............
52 // .............
53 // .............
54 // The message can then be written onto the grid in a zigzag going right:
55 // H...o...o...!
56 // .e.l.,.W.r.d.
57 // ..l... ...l..
58 // The encrypted message is then read line by line:
59 // Hoo!el,Wrdl l
60
61 // We simply return the message as the 'encrypted' message when there is one rail.
62 // This is because the message is transposed along a single rail without being altered.
63 if self.rails == 1 {
64 return Ok(message.to_string());
65 }
66
67 // Initialise the fence (a simple table)
68 // The form of an entry is (bool, char) => (is_msg_element, msg_element)
69 let mut table = vec![vec![(false, '.'); message.len()]; self.rails];
70
71 //Transpose the message along the fence
72 for (col, element) in message.chars().enumerate() {
73 //Given the column (ith element of the message), determine which row to place the
74 //character on
75 let rail = Railfence::calc_current_rail(col, self.rails);
76 table[rail][col] = (true, element);
77 }
78
79 Ok(table
80 .iter()
81 .flatten()
82 .filter(|(is_element, _)| *is_element)
83 .map(|(_, element)| element)
84 .collect::<String>())
85 }
86
87 /// Decrypt a message using a Railfence cipher.
88 ///
89 /// # Examples
90 /// Basic usage:
91 ///
92 /// ```
93 /// use cipher_crypt::{Cipher, Railfence};
94 ///
95 /// let r = Railfence::new(3);
96 /// assert_eq!("Super-secret message!", r.decrypt("Src s!ue-ertmsaepseeg").unwrap());
97 /// ```
98 ///
99 fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
100 // Decryption process:
101 // First a table is created with a height given by the key and a length
102 // given by the ciphertext length.
103 // e.g.
104 // For a key of 3 and the ciphertext "Hoo!el,Wrdl l" of length 13:
105 // .............
106 // .............
107 // .............
108 // The positions in the table that would be used to encrypt a message are identified
109 // x...x...x...x
110 // .x.x.x.x.x.x.
111 // ..x...x...x..
112 // The ciphertext is then written onto the identified positions, line by line
113 // H...o...o...!
114 // .e.l.,.W.r.d.
115 // ..l... ...l..
116 // The decrypted message is then read in a zigzag:
117 // Hello, World!
118
119 // As mentioned previously, a single rail means that the original message has not been
120 // altered
121 if self.rails == 1 {
122 return Ok(ciphertext.to_string());
123 }
124
125 let mut table = vec![vec![(false, '.'); ciphertext.len()]; self.rails];
126
127 // Traverse the table and mark the elements that will be filled by the cipher text
128 for col in 0..ciphertext.len() {
129 let rail = Railfence::calc_current_rail(col, self.rails);
130 table[rail][col].0 = true;
131 }
132
133 // Fill the identified positions in the table with the ciphertext, line by line
134 let mut ct_chars = ciphertext.chars();
135 'outer: for row in &mut table {
136 // For each element in the row, determine if a char should be placed there
137 for element in row.iter_mut() {
138 if element.0 {
139 if let Some(c) = ct_chars.next() {
140 *element = (element.0, c);
141 } else {
142 // We have transposed all chars of the cipher text
143 break 'outer;
144 }
145 }
146 }
147 }
148
149 // From the transposed cipher text construct the original message
150 let mut message = String::new();
151 for col in 0..ciphertext.len() {
152 // For this column, determine which row we should read from to get the next char
153 // of the message
154 let rail = Railfence::calc_current_rail(col, self.rails);
155 message.push(table[rail][col].1);
156 }
157
158 Ok(message)
159 }
160}
161
162impl Railfence {
163 /// For a given column and the total number of 'rails' (rows), determine the current rail
164 /// that should be referenced.
165 ///
166 fn calc_current_rail(col: usize, total_rails: usize) -> usize {
167 // In the Railfence cipher the letters are placed diagonally in a zigzag,
168 // so, with a key of 4 say, the row numbers will go
169 // 0, 1, 2, 3, 2, 1, 0, 1, 2, 3, 2, 1, 0, ...
170 // This repeats with a cycle (or period) given by (2*key - 2)
171 // [0, 1, 2, 3, 2, 1], [0, 1, 2, 3, 2, 1], 0, ...
172 // This cycle is always even.
173 let cycle = 2 * total_rails - 2;
174
175 // For the first half of a cycle, the row is given by the index,
176 // but for the second half it decreases and is therefore given by the reverse index,
177 // the distance from the end of the cycle.
178 if col % cycle <= cycle / 2 {
179 col % cycle
180 } else {
181 cycle - col % cycle
182 }
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn encrypt_test() {
192 let message = "attackatdawn";
193 let r = Railfence::new(6);
194 assert_eq!("awtantdatcak", r.encrypt(message).unwrap());
195 }
196
197 #[test]
198 fn encrypt_mixed_case() {
199 let message = "Hello, World!";
200 let r = Railfence::new(3);
201 assert_eq!("Hoo!el,Wrdl l", r.encrypt(message).unwrap());
202 }
203
204 #[test]
205 fn encrypt_short_key() {
206 let message = "attackatdawn";
207 let r = Railfence::new(1);
208 assert_eq!("attackatdawn", r.encrypt(message).unwrap());
209 }
210
211 #[test]
212 fn encrypt_long_key() {
213 let message = "attackatdawn";
214 let r = Railfence::new(20);
215 assert_eq!("attackatdawn", r.encrypt(message).unwrap());
216 }
217
218 #[test]
219 fn decrypt_test() {
220 let message = "awtantdatcak";
221 let r = Railfence::new(6);
222 assert_eq!("attackatdawn", r.decrypt(message).unwrap());
223 }
224
225 #[test]
226 fn decrypt_short_key() {
227 let message = "attackatdawn";
228 let r = Railfence::new(1);
229 assert_eq!("attackatdawn", r.decrypt(message).unwrap());
230 }
231
232 #[test]
233 fn decrypt_mixed_case() {
234 let message = "Hoo!el,Wrdl l";
235 let r = Railfence::new(3);
236 assert_eq!("Hello, World!", r.decrypt(message).unwrap());
237 }
238
239 #[test]
240 fn decrypt_long_key() {
241 let message = "attackatdawn";
242 let r = Railfence::new(20);
243 assert_eq!("attackatdawn", r.decrypt(message).unwrap());
244 }
245
246 #[test]
247 #[should_panic]
248 fn incorrect_key_test() {
249 Railfence::new(0);
250 }
251
252 #[test]
253 fn unicode_test() {
254 let r = Railfence::new(3);
255 let message = "ÂƮƮäƈķ ɑƬ Ðawŋ ✓";
256 assert_eq!("ÂƈƬwƮäķɑ aŋ✓Ʈ Ð ", r.encrypt(message).unwrap());
257 }
258}