old_crypto_rs/transposition/
regular.rs

1//! Transposition cipher implementation.
2//!
3//! This module implements a columnar transposition cipher, which rearranges the plaintext
4//! by writing it in rows of a fixed length (determined by the key), then reading out the
5//! columns in an order determined by the alphabetical order of the key letters.
6//!
7//! # Examples
8//!
9//! ```
10//! use old_crypto_rs::{Block, Transposition};
11//!
12//! let cipher = Transposition::new("ZEBRAS").unwrap();
13//! let plaintext = b"WEAREDISCOVEREDFLEEATONCE";
14//! let mut ciphertext = vec![0u8; plaintext.len()];
15//! cipher.encrypt(&mut ciphertext, plaintext);
16//! ```
17//!
18
19use crate::{helpers, Block};
20
21use eyre::Result;
22use crate::error::Error;
23
24/// A columnar transposition cipher.
25/// 
26#[derive(Debug)]
27pub struct Transposition {
28    #[allow(dead_code)]
29    key: String,
30    tkey: Vec<u8>,
31}
32
33impl Transposition {
34    /// Creates a new regular columnar transposition cipher.
35    pub fn new(key: &str) -> Result<Self> {
36        if key.is_empty() {
37            return Err(Error::EmptyKeys.into());
38        }
39        Ok(Transposition {
40            key: key.to_string(),
41            tkey: helpers::to_numeric(key),
42        })
43    }
44}
45
46impl Block for Transposition {
47    fn block_size(&self) -> usize {
48        self.tkey.len()
49    }
50
51    /// Encrypts the source data using columnar transposition.
52    ///
53    /// The encryption process works as follows:
54    ///
55    /// 1. **Write plaintext in rows**: The plaintext is written into a grid row by row,
56    ///    with each row having a length equal to the key length.
57    ///
58    /// 2. **Read columns in key order**: The ciphertext is generated by reading the grid
59    ///    column by column in the order determined by the alphabetical order of the key
60    ///    letters. For example, if the key is "ZEBRAS", the columns are read in the order
61    ///    corresponding to "ABERSZ" (alphabetical order of key letters).
62    ///
63    /// # Arguments
64    ///
65    /// * `dst` - The destination buffer where encrypted data will be written
66    /// * `src` - The source plaintext data to encrypt
67    ///
68    /// # Returns
69    ///
70    /// Returns the number of bytes written to `dst` (equal to the length of `src`).
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use old_crypto_rs::{Block, Transposition};
76    ///
77    /// let cipher = Transposition::new("ZEBRAS").unwrap();
78    /// let plaintext = b"WEAREDISCOVEREDFLEEATONCE";
79    /// let mut ciphertext = vec![0u8; plaintext.len()];
80    /// let n = cipher.encrypt(&mut ciphertext, plaintext);
81    /// assert_eq!(n, plaintext.len());
82    /// ```
83    ///
84    fn encrypt(&self, dst: &mut [u8], src: &[u8]) -> usize {
85        let klen = self.tkey.len();
86        let mut offset = 0;
87        for i in 0..klen {
88            let j = self.tkey.iter().position(|&x| x == i as u8).unwrap();
89            let mut curr = j;
90            while curr < src.len() {
91                dst[offset] = src[curr];
92                offset += 1;
93                curr += klen;
94            }
95        }
96        src.len()
97    }
98
99    /// Decrypts the source data using columnar transposition.
100    ///
101    /// The decryption process reverses the encryption:
102    ///
103    /// 1. **Determine column structure**: Calculate how many complete rows exist and how
104    ///    many columns have an extra character (when the plaintext length doesn't divide
105    ///    evenly by the key length).
106    ///
107    /// 2. **Read columns in key order**: Read the ciphertext column by column in the order
108    ///    determined by the alphabetical order of the key letters. Each column is read
109    ///    according to its calculated length.
110    ///
111    /// 3. **Write back row by row**: Place each character into its original position in
112    ///    the grid, effectively reconstructing the original row-by-row layout of the
113    ///    plaintext.
114    ///
115    /// # Arguments
116    ///
117    /// * `dst` - The destination buffer where decrypted data will be written
118    /// * `src` - The source ciphertext data to decrypt
119    ///
120    /// # Returns
121    ///
122    /// Returns the number of bytes written to `dst` (equal to the length of `src`),
123    /// or 0 if the key length is 0 or the source is empty.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// use old_crypto_rs::{Block, Transposition};
129    ///
130    /// let cipher = Transposition::new("ZEBRAS").unwrap();
131    /// let ciphertext = b"EVLNACDTESEAROFODEECWIREE";
132    /// let mut plaintext = vec![0u8; ciphertext.len()];
133    /// let n = cipher.decrypt(&mut plaintext, ciphertext);
134    /// assert_eq!(n, ciphertext.len());
135    /// ```
136    ///
137    fn decrypt(&self, dst: &mut [u8], src: &[u8]) -> usize {
138        let klen = self.tkey.len();
139        if klen == 0 || src.is_empty() {
140            return 0;
141        }
142
143        let scol = src.len() / klen;
144        let extra = src.len() % klen;
145
146        let mut current = 0;
147        for j in 0..klen {
148            let ind = self.tkey.iter().position(|&x| x == j as u8).unwrap();
149            let how_many = if ind < extra { scol + 1 } else { scol };
150
151            let mut dst_idx = ind;
152            for k in 0..how_many {
153                dst[dst_idx] = src[current + k];
154                dst_idx += klen;
155            }
156            current += how_many;
157        }
158        src.len()
159    }
160}
161
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    use rstest::rstest;
168
169    #[test]
170    fn test_new_cipher() {
171        let c = Transposition::new("ABCDE").unwrap();
172        assert_eq!(c.key, "ABCDE");
173        assert_eq!(c.tkey, vec![0, 1, 2, 3, 4]);
174    }
175
176    #[test]
177    fn test_new_cipher_empty() {
178        let c = Transposition::new("");
179        assert!(c.is_err());
180    }
181
182    #[test]
183    fn test_transposition_block_size() {
184        let c = Transposition::new("ABCDE").unwrap();
185        assert_eq!(c.block_size(), 5);
186    }
187
188    #[rstest]
189    #[case("ARABESQUE", "AATNIITN2MIHAAXOOTCT2RNXDNENNAOXMB2TW4DTGKP3ES1TISUY3", "ATTACKATDAWNATPOINT42X23XSENDMOREMUNITIONSBYNIGHTX123")]
190    #[case("SUBWAY", "CWI2DUNG3TDP2EEIN1AAATXOIBTTTT4SRTYXAAOXNMOI2KNN3MNSH", "ATTACKATDAWNATPOINT42X23XSENDMOREMUNITIONSBYNIGHTX123")]
191    #[case("PORTABLE", "CA2DIN3KTXMTITO3ROHAP2OIGTANSMSXADIXENTTWTEUB1AN4NNY2", "ATTACKATDAWNATPOINT42X23XSENDMOREMUNITIONSBYNIGHTX123")]
192    #[case("SUBWAY", "AAGGDAVGAAVGFGVVFAVGDAFX", "AFDFADAGAAAAVVVVGFGVGGGX")]
193    #[case("PRIVACY", "DFDDAAVVDGDADDDVGDFFDAAAGDDX", "DGDDDAGDDGAFADDFDADVDVFAADVX")]
194    fn test_transposition_encrypt(#[case] key: &str, #[case] ct: &str, #[case] pt: &str) {
195        let c = Transposition::new(key).unwrap();
196        let mut dst = vec![0u8; pt.len()];
197        c.encrypt(&mut dst, pt.as_bytes());
198        assert_eq!(String::from_utf8(dst).unwrap(), ct);
199    }
200
201    #[rstest]
202    #[case("ARABESQUE", "AATNIITN2MIHAAXOOTCT2RNXDNENNAOXMB2TW4DTGKP3ES1TISUY3", "ATTACKATDAWNATPOINT42X23XSENDMOREMUNITIONSBYNIGHTX123")]
203    #[case("SUBWAY", "CWI2DUNG3TDP2EEIN1AAATXOIBTTTT4SRTYXAAOXNMOI2KNN3MNSH", "ATTACKATDAWNATPOINT42X23XSENDMOREMUNITIONSBYNIGHTX123")]
204    #[case("PORTABLE", "CA2DIN3KTXMTITO3ROHAP2OIGTANSMSXADIXENTTWTEUB1AN4NNY2", "ATTACKATDAWNATPOINT42X23XSENDMOREMUNITIONSBYNIGHTX123")]
205    #[case("SUBWAY", "AAGGDAVGAAVGFGVVFAVGDAFX", "AFDFADAGAAAAVVVVGFGVGGGX")]
206    #[case("PRIVACY", "DFDDAAVVDGDADDDVGDFFDAAAGDDX", "DGDDDAGDDGAFADDFDADVDVFAADVX")]
207    fn test_transposition_decrypt(#[case] key: &str, #[case] ct: &str, #[case] pt: &str) {
208        let c = Transposition::new(key).unwrap();
209        let mut dst = vec![0u8; pt.len()];
210        c.decrypt(&mut dst, ct.as_bytes());
211        assert_eq!(String::from_utf8(dst).unwrap(), pt);
212    }
213
214}