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}