old_crypto_rs/
vigenere.rs

1//! Vigenere cipher implementation.
2//!
3//! The Vigenere cipher is a polyalphabetic substitution cipher, where each plaintext letter is
4//! replaced by a letter some fixed number of positions down the alphabet.  The number of position
5//! shifts is calculated through a simple key, duplicated across the whole plaintext.
6//!
7//! Essentially, the key creates a Caesar cipher for each letter in the key.
8//!
9//! [Vigenère Cipher](https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher)
10//!
11//! # Examples
12//!
13//! ```
14//! use old_crypto_rs::Block;
15//! use old_crypto_rs::VigenereCipher;
16//!
17//! let cipher = VigenereCipher::new("KEY");
18//! let plaintext = b"HELLO";
19//! let mut ciphertext = vec![0u8; plaintext.len()];
20//! cipher.encrypt(&mut ciphertext, plaintext);
21//! assert_eq!(&ciphertext, b"RIJVS");
22//! ```
23//!
24use crate::Block;
25
26use eyre::Result;
27use crate::error::Error;
28
29/// A Vigenere cipher implementation.
30///
31/// This cipher uses a keyword to perform a series of Caesar ciphers on the plaintext.
32/// It is a classic example of a polyalphabetic substitution cipher.
33/// 
34#[derive(Debug)]
35pub struct VigenereCipher {
36    /// The numeric key values (0-25) derived from the key string.
37    key: Vec<u8>,
38}
39
40impl VigenereCipher {
41    /// Creates a new Vigenere cipher with the given key string.
42    ///
43    /// Non-alphabetic characters in the key are ignored. The key is converted
44    /// to uppercase before processing.
45    ///
46    /// # Arguments
47    ///
48    /// * `key` - The string used to derive the shift values.
49    ///
50    pub fn new(key: &str) -> Self {
51        let key_vec = key.to_ascii_uppercase().as_bytes().iter()
52            .filter(|&&b| b >= b'A' && b <= b'Z')
53            .map(|&b| b - b'A')
54            .collect::<Vec<_>>();
55        VigenereCipher {
56            key: key_vec,
57        }
58    }
59}
60
61/// Core function to perform modular addition of two numeric vectors.
62///
63/// This function adds the corresponding elements of the `plain` and `key` vectors
64/// modulo 26. This is used as the basic operation for both encryption and decryption
65/// in the Vigenere cipher.
66///
67/// # Arguments
68///
69/// * `plain` - A vector of numeric values (0-25) to be shifted.
70/// * `key` - A vector of numeric values (0-25) representing the shift amounts.
71///
72/// # Returns
73///
74/// Returns a `Result` containing the resulting numeric vector if the inputs are
75/// valid and have the same length, or an error otherwise.
76/// 
77pub(crate) fn encode_one(plain: Vec<u8>, key: Vec<u8>) -> Result<Vec<u8>> {
78    if plain.is_empty() || key.is_empty() {
79        return Err(Error::EmptyInput.into());
80    }
81
82    if plain.len() != key.len() {
83        return Err(Error::LengthMismatch(plain.len(), key.len()).into());
84    }
85    
86    let ct = plain.iter().zip(key.iter()).map(|(p, k)| (p + k) % 26).collect::<Vec<_>>();
87    Ok(ct)
88}
89
90impl Block for VigenereCipher {
91    /// Returns the length of the Vigenere key.
92    fn block_size(&self) -> usize {
93        self.key.len()
94    }
95
96    /// Encrypts source bytes into destination buffer using the Vigenere cipher.
97    ///
98    /// This method maps source characters 'A'-'Z' to values 0-25, applies the
99    /// Vigenere shift from the key, and then maps the result back to uppercase letters.
100    /// Non-alphabetic characters are currently handled by applying the shift to their
101    /// raw ASCII value, though most usage expects only 'A'-'Z' input.
102    ///
103    /// # Arguments
104    ///
105    /// * `dst` - The destination buffer for the encrypted bytes.
106    /// * `src` - The source plaintext bytes to encrypt.
107    ///
108    /// # Returns
109    ///
110    /// Returns the number of bytes written to `dst`.
111    /// 
112    fn encrypt(&self, dst: &mut [u8], src: &[u8]) -> usize {
113        if src.is_empty() || self.key.is_empty() {
114            return 0;
115        }
116
117        let mut plain = Vec::with_capacity(src.len());
118        let mut key = Vec::with_capacity(src.len());
119
120        for (i, &p) in src.iter().enumerate() {
121            if i >= dst.len() {
122                break;
123            }
124            // Assumes src is A-Z (ASCII 65-90)
125            let p_val = if p >= b'A' && p <= b'Z' {
126                p - b'A'
127            } else {
128                p
129            };
130            plain.push(p_val);
131            key.push(self.key[i % self.key.len()]);
132        }
133
134        let ct_vals = match encode_one(plain, key) {
135            Ok(v) => v,
136            Err(_) => return 0,
137        };
138
139        for (i, &val) in ct_vals.iter().enumerate() {
140            dst[i] = val + b'A';
141        }
142
143        ct_vals.len()
144    }
145
146    /// Decrypts source bytes into destination buffer using the Vigenere cipher.
147    ///
148    /// This method reverses the Vigenere shift by subtracting the key values
149    /// from the ciphertext values modulo 26.
150    ///
151    /// # Arguments
152    ///
153    /// * `dst` - The destination buffer for the decrypted bytes.
154    /// * `src` - The source ciphertext bytes to decrypt.
155    ///
156    /// # Returns
157    ///
158    /// Returns the number of bytes written to `dst`.
159    /// 
160    fn decrypt(&self, dst: &mut [u8], src: &[u8]) -> usize {
161        if src.is_empty() || self.key.is_empty() {
162            return 0;
163        }
164
165        let mut cipher = Vec::with_capacity(src.len());
166        let mut neg_key = Vec::with_capacity(src.len());
167
168        for (i, &c) in src.iter().enumerate() {
169            if i >= dst.len() {
170                break;
171            }
172            let c_val = if c >= b'A' && c <= b'Z' {
173                c - b'A'
174            } else {
175                c
176            };
177            cipher.push(c_val);
178            // Vigenere decryption: (c - k) % 26
179            // We can use encode_one by providing -k mod 26
180            let k = self.key[i % self.key.len()];
181            neg_key.push((26 - k) % 26);
182        }
183
184        let pt_vals = match encode_one(cipher, neg_key) {
185            Ok(v) => v,
186            Err(_) => return 0,
187        };
188
189        for (i, &val) in pt_vals.iter().enumerate() {
190            dst[i] = val + b'A';
191        }
192
193        pt_vals.len()
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn test_encode_one_valid_input() {
203        let plain = vec![7, 4, 11, 11, 14]; // "HELLO"
204        let key = vec![10, 4, 24, 18, 19]; // "KEYST"
205        let result = encode_one(plain, key).unwrap();
206        let expected = vec![17, 8, 9, 3, 7]; // (H+K)%26, (E+E)%26, etc.
207        assert_eq!(result, expected);
208    }
209
210    #[test]
211    fn test_encode_one_empty_plaintext() {
212        let plain = vec![];
213        let key = vec![1, 2, 3];
214        let result = encode_one(plain, key);
215        assert!(result.is_err());
216        assert_eq!(result.unwrap_err().to_string(), "Empty input");
217    }
218
219    #[test]
220    fn test_encode_one_empty_key() {
221        let plain = vec![1, 2, 3];
222        let key = vec![];
223        let result = encode_one(plain, key);
224        assert!(result.is_err());
225        assert_eq!(result.unwrap_err().to_string(), "Empty input");
226    }
227
228    #[test]
229    fn test_encode_one_mismatched_lengths() {
230        let plain = vec![1, 2, 3];
231        let key = vec![1, 2];
232        let result = encode_one(plain, key);
233        assert!(result.is_err());
234        assert_eq!(result.unwrap_err().to_string(), "Every input must have the same length: 3 vs 2");
235    }
236
237    #[test]
238    fn test_vigenere_cipher_encrypt_decrypt() {
239        let cipher = VigenereCipher::new("KEY");
240        let pt = b"HELLO";
241        let mut ct = vec![0u8; pt.len()];
242        cipher.encrypt(&mut ct, pt);
243        assert_eq!(&ct, b"RIJVS");
244
245        let mut dec = vec![0u8; ct.len()];
246        cipher.decrypt(&mut dec, &ct);
247        assert_eq!(&dec, pt);
248    }
249
250    #[test]
251    fn test_vigenere_cipher_multi_block() {
252        let cipher = VigenereCipher::new("ABC"); // Shift by 0, 1, 2
253        let pt = b"AAAAAA";
254        let mut ct = vec![0u8; pt.len()];
255        cipher.encrypt(&mut ct, pt);
256        assert_eq!(&ct, b"ABCABC");
257
258        let mut dec = vec![0u8; ct.len()];
259        cipher.decrypt(&mut dec, &ct);
260        assert_eq!(&dec, pt);
261    }
262
263    #[test]
264    fn test_wikipedia_example_lemon() {
265        // From Wikipedia: Plaintext "ATTACKATDAWN", Key "LEMON", Ciphertext "LXFOPVEFRNHR"
266        let cipher = VigenereCipher::new("LEMON");
267        let pt = b"ATTACKATDAWN";
268        let mut ct = vec![0u8; pt.len()];
269        cipher.encrypt(&mut ct, pt);
270        assert_eq!(&ct, b"LXFOPVEFRNHR");
271
272        let mut dec = vec![0u8; ct.len()];
273        cipher.decrypt(&mut dec, &ct);
274        assert_eq!(&dec, pt);
275    }
276
277    #[test]
278    fn test_wikipedia_example_babbage() {
279        // From Wikipedia: Babbage's challenge from Thwaites used "TWO" and "COMBINED"
280        // Let's test with one of them.
281        let cipher = VigenereCipher::new("COMBINED");
282        let pt = b"DEFENDTHEEASTWALLOFTHECASTLE";
283        let mut ct = vec![0u8; pt.len()];
284        cipher.encrypt(&mut ct, pt);
285        
286        let mut dec = vec![0u8; ct.len()];
287        cipher.decrypt(&mut dec, &ct);
288        assert_eq!(&dec, pt);
289    }
290}
291