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}