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}