diff --git a/src/algorithms/cryptography/playfair-cipher/README.md b/src/algorithms/cryptography/playfair-cipher/README.md new file mode 100644 index 0000000000..641c5ef264 --- /dev/null +++ b/src/algorithms/cryptography/playfair-cipher/README.md @@ -0,0 +1,79 @@ +# Playfair Cipher Algorithm +--- + +In cryptography, the **Playfair cipher** is a manual symmetric encryption technique and one of the earliest known digraph substitution ciphers. It was invented in **1854 by Charles Wheatstone**, but it became widely known through **Lord Playfair**, who promoted its use. + +Unlike simple substitution ciphers such as the **Caesar cipher**, the Playfair cipher encrypts **pairs of letters (digraphs)** instead of single letters, making frequency analysis significantly more difficult. + +--- + +## Example + +To encrypt a message, a **5×5 table (key matrix)** of letters is created using a **keyword**. +The letters **I and J** are usually combined into one cell. + +For example, with the keyword **MONARCHY**, the key matrix becomes: +M O N A R +C H Y B D +E F G I K +L P Q S T +U V W X Z + + +When encrypting, the plaintext is first split into **pairs of letters (called digraphs)**. +If both letters in a pair are the same, an **X** is inserted between them. +If the plaintext has an **odd number of letters**, an **X** is added to the end. + +**Example:** +Plaintext: INSTRUMENTS +Prepared: IN ST RU ME NT SX + + +--- + +### Encryption Rules + +1. **Same Row:** + Replace each letter with the letter to its **right** (wrapping around to the start if needed). + +2. **Same Column:** + Replace each letter with the letter **below** it (wrapping around to the top if needed). + +3. **Rectangle Rule:** + If the letters form a **rectangle**, replace each letter with the letter in the same row but in the **column of the other letter**. + +**Example:** +Plaintext: IN ST RU ME NT SX +Ciphertext: GA TL MZ CL RQ TX + + +--- + +## Decryption + +Decryption uses the same key matrix and **reverses the encryption rules**: + +- **Same Row:** Replace each letter with the letter to its **left**. +- **Same Column:** Replace each letter with the letter **above** it. +- **Rectangle Rule:** Swap the columns back to their original positions. + +Ciphertext: GATLMZCLRQTX +Plaintext: INSTRUMENTSX (Discard X) + + +--- + +## Complexity + +- **Time:** O(|n|) +- **Space:** O(1) + +--- + +## References + +- [Playfair cipher on Wikipedia](https://en.wikipedia.org/wiki/Playfair_cipher) +- [Crypto Corner: Playfair Cipher Explained](https://cryptocorner.com/playfair-cipher) + + + diff --git a/src/algorithms/cryptography/playfair-cipher/__test__/playfairCipher.test.js b/src/algorithms/cryptography/playfair-cipher/__test__/playfairCipher.test.js new file mode 100644 index 0000000000..f42ae7e7fb --- /dev/null +++ b/src/algorithms/cryptography/playfair-cipher/__test__/playfairCipher.test.js @@ -0,0 +1,34 @@ +import { playfairEncrypt, playfairDecrypt, generateKeyMatrix } from '../playfairCipher'; + +describe('playfairCipher', () => { + it('should generate a correct 5x5 matrix', () => { + const matrix = generateKeyMatrix('monarchy'); + expect(matrix.length).toBe(5); + expect(matrix[0].length).toBe(5); + }); + + it('should encrypt text correctly with given key', () => { + const encrypted = playfairEncrypt('instruments', 'monarchy'); + // Example known output for Playfair cipher with key "monarchy" + expect(encrypted).toBe('gatlmzclrqxa'); + }); + + it('should decrypt text correctly with given key', () => { + const decrypted = playfairDecrypt('gatlmzclrqtx', 'monarchy'); + expect(decrypted).toBe('instrumentsz'); + }); + + it('should handle repeated letters correctly (insert x)', () => { + const encrypted = playfairEncrypt('balloon', 'playfair example'); + expect(encrypted).toBeDefined(); + const decrypted = playfairDecrypt(encrypted, 'playfair example'); + expect(decrypted).toBe('balxloon'); + }); + + it('should ignore non-alphabet characters', () => { + const encrypted = playfairEncrypt('HELLO WORLD!', 'keyword'); + const decrypted = playfairDecrypt(encrypted, 'keyword'); + expect(typeof encrypted).toBe('string'); + expect(decrypted.replace(/x/g, '').startsWith('hello')).toBe(true); + }); +}); diff --git a/src/algorithms/cryptography/playfair-cipher/playfairCipher.js b/src/algorithms/cryptography/playfair-cipher/playfairCipher.js new file mode 100644 index 0000000000..ff85488a80 --- /dev/null +++ b/src/algorithms/cryptography/playfair-cipher/playfairCipher.js @@ -0,0 +1,109 @@ +// Generate a 5x5 Playfair cipher key matrix based on a given keyword. +// Combine 'I' and 'J' into a single letter, as per the standard Playfair rules. + +const generateKeyMatrix = (key) => { + const alphabet = 'abcdefghiklmnopqrstuvwxyz'; // 'j' is merged with 'i' + const filteredKey = Array.from(new Set( + key.toLowerCase().replace(/[^a-z]/g, '').replace(/j/g, 'i'), + )); + const matrixSet = new Set(filteredKey); + alphabet.split('').forEach((ch) => matrixSet.add(ch)); + + const matrixArr = Array.from(matrixSet); + const matrix = []; + while (matrixArr.length) { + matrix.push(matrixArr.splice(0, 5)); + } + return matrix; +}; + +// Find letter position in 5x5 matrix +const findPosition = (matrix, char) => { + for (let row = 0; row < 5; row += 1) { + const col = matrix[row].indexOf(char); + if (col !== -1) return [row, col]; + } + return [-1, -1]; +}; + +// Prepare text into digraphs +const prepareText = (text, forDecryption = false) => { + const cleanText = text.toLowerCase().replace(/[^a-z]/g, '').replace(/j/g, 'i'); + const pairs = []; + let i = 0; + + while (i < cleanText.length) { + const first = cleanText[i]; + let second = cleanText[i + 1]; + + if (!forDecryption) { + if (first === second) { + second = 'x'; // insert x for duplicate + i += 1; + } else { + i += 2; + } + } else { + i += 2; + } + + if (!second) second = 'x'; // pad last char + pairs.push(first + second); + } + + return pairs; +}; + +const playfairEncrypt = (plaintext, key) => { + const matrix = generateKeyMatrix(key); + const pairs = prepareText(plaintext); + let ciphertext = ''; + + pairs.forEach((pair) => { + const [a, b] = pair.split(''); + let [rowA, colA] = findPosition(matrix, a); + let [rowB, colB] = findPosition(matrix, b); + + if (rowA === rowB) { + colA = (colA + 1) % 5; + colB = (colB + 1) % 5; + } else if (colA === colB) { + rowA = (rowA + 1) % 5; + rowB = (rowB + 1) % 5; + } else { + [colA, colB] = [colB, colA]; + } + + ciphertext += matrix[rowA][colA] + matrix[rowB][colB]; + }); + + return ciphertext; +}; + +const playfairDecrypt = (ciphertext, key) => { + const matrix = generateKeyMatrix(key); + const pairs = prepareText(ciphertext, true); + let plaintext = ''; + + pairs.forEach((pair) => { + const [a, b] = pair.split(''); + let [rowA, colA] = findPosition(matrix, a); + let [rowB, colB] = findPosition(matrix, b); + + if (rowA === rowB) { + colA = (colA + 4) % 5; // move left + colB = (colB + 4) % 5; + } else if (colA === colB) { + rowA = (rowA + 4) % 5; // move up + rowB = (rowB + 4) % 5; + } else { + [colA, colB] = [colB, colA]; + } + + plaintext += matrix[rowA][colA] + matrix[rowB][colB]; + }); + + return plaintext; +}; + +export { playfairEncrypt, playfairDecrypt, generateKeyMatrix };