old_crypto_rs/playfair.rs
1//! Playfair cipher implementation.
2//!
3//! The Playfair cipher is a manual symmetric encryption technique and was the first literal
4//! digraph substitution cipher. The scheme was invented in 1854 by Charles Wheatstone but
5//! bears the name of Lord Playfair who promoted the use of the cipher.
6//!
7//! The Playfair cipher encrypts pairs of letters (bigrams or digraphs), instead of single
8//! letters as in the simple substitution cipher. The key is a 5×5 grid of letters constructed
9//! using a keyword. The letters I and J are typically combined into a single cell.
10//!
11//! # Algorithm
12//!
13//! 1. Create a 5×5 matrix using a keyword (duplicates removed) followed by remaining alphabet letters
14//! 2. Split plaintext into pairs of letters (digraphs)
15//! 3. Apply transformation rules based on the position of letter pairs in the matrix:
16//! - Same row: shift each letter one position to the right (wrapping around)
17//! - Same column: shift each letter one position down (wrapping around)
18//! - Rectangle: swap columns of the two letters
19//!
20//! # Example
21//!
22//! ```rust
23//! use old_crypto_rs::{Block, PlayfairCipher};
24//!
25//! let cipher = PlayfairCipher::new("PLAYFAIREXAMPLE");
26//! let plaintext = b"HIDETHEGOLDINTHETREXESTUMP";
27//! let mut ciphertext = vec![0u8; plaintext.len()];
28//! cipher.encrypt(&mut ciphertext, plaintext);
29//! ```
30//!
31use crate::Block;
32use crate::helpers;
33
34const ALPHABET: &str = "ABCDEFGHIKLMNOPQRSTUVWXYZ";
35const OP_ENCRYPT: u8 = 1;
36const OP_DECRYPT: u8 = 4;
37
38/// Playfair cipher implementation using a 5×5 keyed matrix.
39///
40/// The `PlayfairCipher` struct holds the cipher key and maintains bidirectional tables
41/// between characters and their positions in the 5×5 Playfair matrix. This allows for
42/// efficient encryption and decryption operations.
43///
44/// # Fields
45///
46/// * `key` - The condensed key string used to build the cipher matrix
47/// * `i2c` - Table from character (as u8) to its `Couple` position in the matrix
48/// * `c2i` - Table from matrix position to its character (as u8)
49///
50pub struct PlayfairCipher {
51 #[allow(dead_code)]
52 key: String,
53 i2c: [Couple; 256],
54 c2i: [u8; 25],
55}
56
57/// Represents a coordinate pair (row, column) in the 5×5 Playfair matrix.
58///
59/// A `Couple` is used to represent either:
60/// - The position of a character in the Playfair matrix (as a coordinate)
61/// - A pair of characters to be encrypted/decrypted (as indices)
62///
63/// # Fields
64///
65/// * `r` - Row index (0-4) or first character of a bigram
66/// * `c` - Column index (0-4) or second character of a bigram
67///
68#[derive(Clone, Copy, PartialEq, Eq, Debug)]
69struct Couple {
70 r: u8,
71 c: u8,
72}
73
74const INVALID_COUPLE: Couple = Couple { r: 0xFF, c: 0xFF };
75
76impl PlayfairCipher {
77 fn lookup(&self, ch: u8) -> Couple {
78 let couple = self.i2c[ch as usize];
79 if couple.r == 0xFF {
80 panic!("invalid character");
81 }
82 couple
83 }
84
85 /// Transforms a pair of characters using the Playfair cipher rules.
86 ///
87 /// This is the core encryption/decryption function that applies the Playfair transformation
88 /// rules based on the relative positions of two characters in the matrix.
89 ///
90 /// # Arguments
91 ///
92 /// * `pt` - A `Couple` where `r` and `c` represent the two characters to transform
93 /// * `opt` - Operation mode: `OP_ENCRYPT` (1) for encryption, `OP_DECRYPT` (4) for decryption
94 ///
95 /// # Returns
96 ///
97 /// A `Couple` containing the transformed character pair
98 ///
99 /// # Transformation Rules
100 ///
101 /// 1. **Same row**: Shift each character by `opt` positions to the right (modulo 5)
102 /// 2. **Same column**: Shift each character by `opt` positions down (modulo 5)
103 /// 3. **Rectangle**: Swap the columns of the two characters
104 ///
105 fn transform(&self, pt: Couple, opt: u8) -> Couple {
106 let bg1 = self.lookup(pt.r);
107 let bg2 = self.lookup(pt.c);
108 if bg1.r == bg2.r {
109 let ct1 = Couple { r: bg1.r, c: (bg1.c + opt) % 5 };
110 let ct2 = Couple { r: bg2.r, c: (bg2.c + opt) % 5 };
111 return Couple {
112 r: self.c2i[(ct1.r as usize) * 5 + ct1.c as usize],
113 c: self.c2i[(ct2.r as usize) * 5 + ct2.c as usize],
114 };
115 }
116 if bg1.c == bg2.c {
117 let ct1 = Couple { r: (bg1.r + opt) % 5, c: bg1.c };
118 let ct2 = Couple { r: (bg2.r + opt) % 5, c: bg2.c };
119 return Couple {
120 r: self.c2i[(ct1.r as usize) * 5 + ct1.c as usize],
121 c: self.c2i[(ct2.r as usize) * 5 + ct2.c as usize],
122 };
123 }
124 let ct1 = Couple { r: bg1.r, c: bg2.c };
125 let ct2 = Couple { r: bg2.r, c: bg1.c };
126 Couple {
127 r: self.c2i[(ct1.r as usize) * 5 + ct1.c as usize],
128 c: self.c2i[(ct2.r as usize) * 5 + ct2.c as usize],
129 }
130 }
131
132 /// Creates a new Playfair cipher with the specified key.
133 ///
134 /// The key is used to construct a 5×5 matrix by:
135 /// 1. Condensing the key (removing duplicates and converting to uppercase)
136 /// 2. Appending the remaining alphabet letters (I and J are combined)
137 /// 3. Filling the matrix row by row with these characters
138 ///
139 /// # Arguments
140 ///
141 /// * `key` - The keyword used to generate the cipher matrix
142 ///
143 /// # Returns
144 ///
145 /// A new `PlayfairCipher` instance with initialized mapping tables
146 ///
147 /// # Example
148 ///
149 /// ```rust
150 /// use old_crypto_rs::PlayfairCipher;
151 ///
152 /// let cipher = PlayfairCipher::new("PLAYFAIREXAMPLE");
153 /// ```
154 ///
155 pub fn new(key: &str) -> Self {
156 let key = key.to_ascii_uppercase().replace('J', "I");
157 let condensed_key = helpers::condense(&format!("{}{}", key, ALPHABET));
158 let mut i2c = [INVALID_COUPLE; 256];
159 let mut c2i = [0u8; 25];
160
161 let mut ind = 0;
162 let key_bytes = condensed_key.as_bytes();
163 for i in 0..5 {
164 for j in 0..5 {
165 let c = key_bytes[ind];
166 let couple = Couple { r: i as u8, c: j as u8 };
167 i2c[c as usize] = couple;
168 c2i[i * 5 + j] = c;
169 ind += 1;
170 }
171 }
172
173 PlayfairCipher {
174 key: condensed_key,
175 i2c,
176 c2i,
177 }
178 }
179}
180
181impl Block for PlayfairCipher {
182 /// BlockSize is part of the interface
183 fn block_size(&self) -> usize {
184 2
185 }
186
187 /// Encrypts plaintext using the Playfair cipher.
188 ///
189 /// This method processes the input plaintext in pairs of characters (digraphs) and applies
190 /// the Playfair transformation rules. If the plaintext has an odd length, an 'X' is
191 /// automatically appended as padding.
192 ///
193 /// # Arguments
194 ///
195 /// * `dst` - Destination buffer where the ciphertext will be written. Must be large enough
196 /// to hold the expanded plaintext (considering double letters and padding).
197 /// A safe size is at least 2x the source length.
198 /// * `src` - Source plaintext bytes to encrypt. Each byte should represent an uppercase
199 /// letter from the Playfair alphabet.
200 ///
201 /// # Returns
202 ///
203 /// The number of bytes written to the destination buffer (always even).
204 ///
205 /// # Example
206 ///
207 /// ```rust
208 /// use old_crypto_rs::{Block, PlayfairCipher};
209 ///
210 /// let cipher = PlayfairCipher::new("PLAYFAIREXAMPLE");
211 /// let plaintext = b"HIDETHEGOLD";
212 /// let mut ciphertext = vec![0u8; 12]; // Account for potential padding
213 /// let written = cipher.encrypt(&mut ciphertext, plaintext);
214 /// assert_eq!(written, 12); // 11 chars + 1 'X' padding = 12
215 /// ```
216 ///
217 fn encrypt(&self, dst: &mut [u8], src: &[u8]) -> usize {
218 let src = String::from_utf8_lossy(src).to_ascii_uppercase().replace('J', "I");
219 let src = helpers::fix_double_aligned(&src, 'X');
220 let mut src_vec = src.as_bytes().to_vec();
221 if src_vec.len() % 2 == 1 {
222 src_vec.push(b'X');
223 }
224
225 for i in (0..src_vec.len()).step_by(2) {
226 let bg = self.transform(Couple { r: src_vec[i], c: src_vec[i+1] }, OP_ENCRYPT);
227 dst[i] = bg.r;
228 dst[i+1] = bg.c;
229 }
230 src_vec.len()
231 }
232
233 /// Decrypts ciphertext using the Playfair cipher.
234 ///
235 /// This method processes the input ciphertext in pairs of characters (digraphs) and applies
236 /// the inverse Playfair transformation rules to recover the original plaintext.
237 ///
238 /// # Arguments
239 ///
240 /// * `dst` - Destination buffer where the plaintext will be written. Must be at least as
241 /// large as the source length.
242 /// * `src` - Source ciphertext bytes to decrypt. Must have an even length, as Playfair
243 /// operates on character pairs.
244 ///
245 /// # Returns
246 ///
247 /// The number of bytes written to the destination buffer (equal to source length).
248 ///
249 /// # Panics
250 ///
251 /// Panics if the source ciphertext has an odd number of bytes, as this violates the
252 /// Playfair cipher's requirement to operate on digraphs.
253 ///
254 /// # Example
255 ///
256 /// ```rust
257 /// use old_crypto_rs::{Block, PlayfairCipher};
258 ///
259 /// let cipher = PlayfairCipher::new("PLAYFAIREXAMPLE");
260 /// let ciphertext = b"BMODZBXDNABEKUDMUIXMMOUVIF";
261 /// let mut plaintext = vec![0u8; ciphertext.len()];
262 /// let written = cipher.decrypt(&mut plaintext, ciphertext);
263 /// assert_eq!(written, ciphertext.len());
264 /// ```
265 ///
266 fn decrypt(&self, dst: &mut [u8], src: &[u8]) -> usize {
267 if src.len() % 2 == 1 {
268 panic!("odd number of elements");
269 }
270
271 for i in (0..src.len()).step_by(2) {
272 let bg = self.transform(Couple { r: src[i], c: src[i+1] }, OP_DECRYPT);
273 dst[i] = bg.r;
274 dst[i+1] = bg.c;
275 }
276 src.len()
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_new_cipher() {
286 let _c = PlayfairCipher::new("ARABESQUE");
287 }
288
289 #[test]
290 fn test_playfair_cipher_block_size() {
291 let c = PlayfairCipher::new("ARABESQUE");
292 assert_eq!(c.block_size(), 2);
293 }
294
295 #[test]
296 fn test_playfair_cipher_encrypt() {
297 let c = PlayfairCipher::new("PLAYFAIREXAMPLE");
298 let pt = b"HIDETHEGOLDINTHETREESTUMP";
299 let ct = b"BMODZBXDNABEKUDMUIXMMOUVIF";
300 let mut dst = vec![0u8; ct.len()];
301 c.encrypt(&mut dst, pt);
302 assert_eq!(dst, ct);
303 }
304
305 #[test]
306 fn test_playfair_cipher_encrypt_x() {
307 let c = PlayfairCipher::new("PLAYFAIREXAMPLE");
308 let pt = b"HID";
309 let ct = b"BMGE";
310 let mut dst = vec![0u8; 4];
311 c.encrypt(&mut dst, pt);
312 assert_eq!(dst, ct);
313 }
314
315 #[test]
316 fn test_playfair_cipher_decrypt() {
317 let c = PlayfairCipher::new("PLAYFAIREXAMPLE");
318 let ct = b"BMODZBXDNABEKUDMUIXMMOUVIF";
319 let pt = b"HIDETHEGOLDINTHETREXESTUMP";
320 let mut dst = vec![0u8; ct.len()];
321 c.decrypt(&mut dst, ct);
322 assert_eq!(dst, pt);
323 }
324
325 #[test]
326 #[should_panic(expected = "odd number of elements")]
327 fn test_playfair_cipher_decrypt_panic() {
328 let c = PlayfairCipher::new("PLAYFAIREXAMPLE");
329 let ct = b"BMO";
330 let mut dst = vec![0u8; 3];
331 c.decrypt(&mut dst, ct);
332 }
333}