old_crypto_rs/adfgvx.rs
1//! ADFGVX cipher implementation.
2//!
3//! The ADFGVX cipher is a field cipher used by the German Army during World War I.
4//! It combines a modified Polybius square with a columnar transposition to provide
5//! encryption. The cipher is named after the six letters (A, D, F, G, V, X) used to
6//! represent the rows and columns of a 6×6 Polybius square, which allows for 36
7//! characters (26 letters + 10 digits).
8//!
9//! The encryption process consists of two stages:
10//! 1. Substitution using a 6×6 Polybius square (keyed with `key1`)
11//! 2. Transposition using columnar transposition (keyed with `key2`)
12//!
13//! # Examples
14//!
15//! ```
16//! # use old_crypto_rs::ADFGVX;
17//! # use old_crypto_rs::Block;
18//! let cipher = ADFGVX::new("PORTABLE", "SUBWAY").unwrap();
19//! let plaintext = b"ATTACKATDAWN";
20//! let mut ciphertext = vec![0u8; 24];
21//! cipher.encrypt(&mut ciphertext, plaintext);
22//! ```
23//!
24use crate::Block;
25use crate::square::SquareCipher;
26use crate::transposition::Transposition;
27
28use std::cell::RefCell;
29
30use eyre::Result;
31
32/// ADFGVX cipher combining Polybius square substitution with columnar transposition.
33///
34/// This structure holds the two components of the ADFGVX cipher:
35/// - A Polybius square cipher using the letters A, D, F, G, V, X
36/// - A columnar transposition cipher for the second encryption stage
37///
38#[derive(Debug)]
39pub struct ADFGVX {
40 sqr: SquareCipher,
41 transp: Transposition,
42 buf: RefCell<Vec<u8>>,
43}
44
45impl ADFGVX {
46 /// Creates a new ADFGVX cipher with the given keys.
47 ///
48 /// # Arguments
49 ///
50 /// * `key1` - The keyword for the Polybius square substitution (should contain unique letters)
51 /// * `key2` - The keyword for the columnar transposition (defines column order)
52 ///
53 /// # Returns
54 ///
55 /// Returns `Ok(ADFGVX)` if both keys are valid, or `Err(String)` with an error message
56 /// if either key is invalid (e.g., empty or contains invalid characters).
57 ///
58 /// # Examples
59 ///
60 /// ```
61 /// # use old_crypto_rs::ADFGVX;
62 /// let cipher = ADFGVX::new("PORTABLE", "SUBWAY").unwrap();
63 /// ```
64 ///
65 /// # Errors
66 ///
67 /// Returns an error if:
68 /// - Either key is empty
69 /// - The keys contain invalid characters
70 /// - The Polybius square or transposition cipher cannot be initialized
71 ///
72 pub fn new(key1: &str, key2: &str) -> Result<Self> {
73 let sqr = SquareCipher::new(key1, "ADFGVX")?;
74 let transp = Transposition::new(key2)?;
75
76 Ok(ADFGVX {
77 sqr,
78 transp,
79 buf: RefCell::new(Vec::new()),
80 })
81 }
82}
83
84impl Block for ADFGVX {
85 /// Returns the block size for the cipher.
86 ///
87 /// The block size is determined by the transposition cipher's block size,
88 /// which is twice the length of the transposition key (since each character
89 /// is first encoded into two ADFGVX characters).
90 ///
91 /// # Returns
92 ///
93 /// The block size in bytes.
94 ///
95 fn block_size(&self) -> usize {
96 self.transp.block_size()
97 }
98
99 /// Encrypts plaintext using the ADFGVX cipher.
100 ///
101 /// The encryption is performed in two stages:
102 /// 1. Each plaintext character is substituted using the Polybius square,
103 /// producing two ADFGVX characters (row and column coordinates)
104 /// 2. The resulting bigrammatic text is transposed using columnar transposition
105 ///
106 /// # Arguments
107 ///
108 /// * `dst` - Destination buffer for the ciphertext (must be large enough)
109 /// * `src` - Source plaintext bytes to encrypt
110 ///
111 /// # Returns
112 ///
113 /// The number of bytes written to `dst`.
114 ///
115 fn encrypt(&self, dst: &mut [u8], src: &[u8]) -> usize {
116 let mut buf = self.buf.borrow_mut();
117 let needed = 2 * src.len();
118 if buf.len() < needed {
119 buf.resize(needed, 0);
120 }
121 let n = self.sqr.encrypt(&mut buf[..needed], src);
122 self.transp.encrypt(dst, &buf[..n])
123 }
124
125 /// Decrypts ciphertext using the ADFGVX cipher.
126 ///
127 /// The decryption is performed by reversing the two encryption stages:
128 /// 1. The columnar transposition is reversed to recover the bigrammatic text
129 /// 2. Each pair of ADFGVX characters is converted back to the original character
130 /// using the Polybius square
131 ///
132 /// # Arguments
133 ///
134 /// * `dst` - Destination buffer for the plaintext (must be large enough)
135 /// * `src` - Source ciphertext bytes to decrypt
136 ///
137 /// # Returns
138 ///
139 /// The number of bytes written to `dst`.
140 ///
141 fn decrypt(&self, dst: &mut [u8], src: &[u8]) -> usize {
142 let mut buf = self.buf.borrow_mut();
143 let needed = src.len();
144 if buf.len() < needed {
145 buf.resize(needed, 0);
146 }
147 let n = self.transp.decrypt(&mut buf[..needed], src);
148 self.sqr.decrypt(dst, &buf[..n])
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use rstest::rstest;
156
157 #[test]
158 fn test_new_cipher() {
159 let _c = ADFGVX::new("PORTABLE", "SUBWAY").unwrap();
160 }
161
162 #[test]
163 fn test_new_cipher_bad_keys() {
164 assert!(ADFGVX::new("PORTABLE", "").is_err());
165 assert!(ADFGVX::new("", "SUBWAY").is_err());
166 }
167
168 #[test]
169 fn test_adfgvx_block_size() {
170 let c = ADFGVX::new("PORTABLE", "SUBWAY").unwrap();
171 assert_eq!(c.block_size(), 6);
172 }
173
174 #[rstest]
175 #[case("PORTABLE", "SUBWAY", "ATTACKATDAWN", "AVFVADAXAAAAVVVVGXGFGDDG")]
176 #[case("NACHTBOMMENWERPER", "PRIVACY", "ATTACKAT1200AM", "DGDDDAGDDGAFADDFDADVDVFAADVX")]
177 fn test_adfgvx_encrypt(#[case] key1: &str, #[case] key2: &str, #[case] pt: &str, #[case] ct: &str) {
178 let c = ADFGVX::new(key1, key2).unwrap();
179 let mut dst = vec![0u8; ct.len()];
180 c.encrypt(&mut dst, pt.as_bytes());
181 assert_eq!(String::from_utf8_lossy(&dst), ct);
182 }
183
184 #[rstest]
185 #[case("PORTABLE", "SUBWAY", "ATTACKATDAWN", "AVFVADAXAAAAVVVVGXGFGDDG")]
186 #[case("NACHTBOMMENWERPER", "PRIVACY", "ATTACKAT1200AM", "DGDDDAGDDGAFADDFDADVDVFAADVX")]
187 fn test_adfgvx_decrypt(#[case] key1: &str, #[case] key2: &str, #[case] pt: &str, #[case] ct: &str) {
188 let c = ADFGVX::new(key1, key2).unwrap();
189 let mut dst = vec![0u8; pt.len()];
190 c.decrypt(&mut dst, ct.as_bytes());
191 assert_eq!(String::from_utf8_lossy(&dst), pt);
192 }
193}