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}