mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-12-19 07:00:35 +08:00
* feat(ciphers): Add PermutationCipher implementation with comprehensive tests - Implement PermutationCipher class for transposition encryption/decryption - Add encrypt() and decrypt() methods with permutation key support - Include robust key validation (1-based positions, no duplicates) - Implement automatic padding for incomplete blocks using 'X' character - Add comprehensive error handling with descriptive exceptions - Create 20+ JUnit test cases covering encryption, decryption, edge cases - Support various key sizes and text processing (spaces removal, case handling) - Include detailed JavaDoc documentation with algorithm explanation Algorithm Details: - Divides plaintext into blocks based on key length - Rearranges characters within each block according to permutation positions - Supports round-trip encryption/decryption with inverse permutation - Handles edge cases: empty strings, single character keys, padding Tests include: basic functionality, different key sizes, error validation, real-world examples, and edge case handling. * Run PermutationCipherTest using Maven * refactor(PermutationCipher): clean up code by removing unnecessary whitespace and comments * fix(tests): remove unnecessary whitespace in test assertion for encryption * fix(tests): correct indentation in assertion for encryption verification --------- Co-authored-by: a <alexanderklmn@gmail.com>
195 lines
6.4 KiB
Java
195 lines
6.4 KiB
Java
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<Integer> 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;
|
|
}
|
|
}
|