package com.thealgorithms.ciphers; import java.util.HashSet; import java.util.Set; /** * A Java implementation of Permutation Cipher. * It is a type of transposition cipher in which the plaintext is divided into blocks * and the characters within each block are rearranged according to a fixed permutation key. * * For example, with key {3, 1, 2} and plaintext "HELLO", the text is divided into blocks * of 3 characters: "HEL" and "LO" (with padding). The characters are then rearranged * according to the key positions. * * @author GitHub Copilot */ public class PermutationCipher { private static final char PADDING_CHAR = 'X'; /** * Encrypts the given plaintext using the permutation cipher with the specified key. * * @param plaintext the text to encrypt * @param key the permutation key (array of integers representing positions) * @return the encrypted text * @throws IllegalArgumentException if the key is invalid */ public String encrypt(String plaintext, int[] key) { validateKey(key); if (plaintext == null || plaintext.isEmpty()) { return plaintext; } // Remove spaces and convert to uppercase for consistent processing String cleanText = plaintext.replaceAll("\\s+", "").toUpperCase(); // Pad the text to make it divisible by key length String paddedText = padText(cleanText, key.length); StringBuilder encrypted = new StringBuilder(); // Process text in blocks of key length for (int i = 0; i < paddedText.length(); i += key.length) { String block = paddedText.substring(i, Math.min(i + key.length, paddedText.length())); encrypted.append(permuteBlock(block, key)); } return encrypted.toString(); } /** * Decrypts the given ciphertext using the permutation cipher with the specified key. * * @param ciphertext the text to decrypt * @param key the permutation key (array of integers representing positions) * @return the decrypted text * @throws IllegalArgumentException if the key is invalid */ public String decrypt(String ciphertext, int[] key) { validateKey(key); if (ciphertext == null || ciphertext.isEmpty()) { return ciphertext; } // Create the inverse permutation int[] inverseKey = createInverseKey(key); StringBuilder decrypted = new StringBuilder(); // Process text in blocks of key length for (int i = 0; i < ciphertext.length(); i += key.length) { String block = ciphertext.substring(i, Math.min(i + key.length, ciphertext.length())); decrypted.append(permuteBlock(block, inverseKey)); } // Remove padding characters from the end return removePadding(decrypted.toString()); } /** * Validates that the permutation key is valid. * A valid key must contain all integers from 1 to n exactly once, where n is the key length. * * @param key the permutation key to validate * @throws IllegalArgumentException if the key is invalid */ private void validateKey(int[] key) { if (key == null || key.length == 0) { throw new IllegalArgumentException("Key cannot be null or empty"); } Set keySet = new HashSet<>(); for (int position : key) { if (position < 1 || position > key.length) { throw new IllegalArgumentException("Key must contain integers from 1 to " + key.length); } if (!keySet.add(position)) { throw new IllegalArgumentException("Key must contain each position exactly once"); } } } /** * Pads the text with padding characters to make its length divisible by the block size. * * @param text the text to pad * @param blockSize the size of each block * @return the padded text */ private String padText(String text, int blockSize) { int remainder = text.length() % blockSize; if (remainder == 0) { return text; } int paddingNeeded = blockSize - remainder; StringBuilder padded = new StringBuilder(text); for (int i = 0; i < paddingNeeded; i++) { padded.append(PADDING_CHAR); } return padded.toString(); } /** * Applies the permutation to a single block of text. * * @param block the block to permute * @param key the permutation key * @return the permuted block */ private String permuteBlock(String block, int[] key) { if (block.length() != key.length) { // Handle case where block is shorter than key (shouldn't happen with proper padding) block = padText(block, key.length); } char[] result = new char[key.length]; char[] blockChars = block.toCharArray(); for (int i = 0; i < key.length; i++) { // Key positions are 1-based, so subtract 1 for 0-based array indexing result[i] = blockChars[key[i] - 1]; } return new String(result); } /** * Creates the inverse permutation key for decryption. * * @param key the original permutation key * @return the inverse key */ private int[] createInverseKey(int[] key) { int[] inverse = new int[key.length]; for (int i = 0; i < key.length; i++) { // The inverse key maps each position to where it should go inverse[key[i] - 1] = i + 1; } return inverse; } /** * Removes padding characters from the end of the decrypted text. * * @param text the text to remove padding from * @return the text without padding */ private String removePadding(String text) { if (text.isEmpty()) { return text; } int i = text.length() - 1; while (i >= 0 && text.charAt(i) == PADDING_CHAR) { i--; } return text.substring(0, i + 1); } /** * Gets the padding character used by this cipher. * * @return the padding character */ public char getPaddingChar() { return PADDING_CHAR; } }