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