Skip to content

Commit c06dc29

Browse files
dheeraj1010alxkm
andauthored
feat(ciphers): Add PermutationCipher implementation(enhance #6697) (#6700)
* 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 <[email protected]>
1 parent 74647e3 commit c06dc29

File tree

2 files changed

+517
-0
lines changed

2 files changed

+517
-0
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package com.thealgorithms.ciphers;
2+
3+
import java.util.HashSet;
4+
import java.util.Set;
5+
6+
/**
7+
* A Java implementation of Permutation Cipher.
8+
* It is a type of transposition cipher in which the plaintext is divided into blocks
9+
* and the characters within each block are rearranged according to a fixed permutation key.
10+
*
11+
* For example, with key {3, 1, 2} and plaintext "HELLO", the text is divided into blocks
12+
* of 3 characters: "HEL" and "LO" (with padding). The characters are then rearranged
13+
* according to the key positions.
14+
*
15+
* @author GitHub Copilot
16+
*/
17+
public class PermutationCipher {
18+
19+
private static final char PADDING_CHAR = 'X';
20+
21+
/**
22+
* Encrypts the given plaintext using the permutation cipher with the specified key.
23+
*
24+
* @param plaintext the text to encrypt
25+
* @param key the permutation key (array of integers representing positions)
26+
* @return the encrypted text
27+
* @throws IllegalArgumentException if the key is invalid
28+
*/
29+
public String encrypt(String plaintext, int[] key) {
30+
validateKey(key);
31+
32+
if (plaintext == null || plaintext.isEmpty()) {
33+
return plaintext;
34+
}
35+
36+
// Remove spaces and convert to uppercase for consistent processing
37+
String cleanText = plaintext.replaceAll("\\s+", "").toUpperCase();
38+
39+
// Pad the text to make it divisible by key length
40+
String paddedText = padText(cleanText, key.length);
41+
42+
StringBuilder encrypted = new StringBuilder();
43+
44+
// Process text in blocks of key length
45+
for (int i = 0; i < paddedText.length(); i += key.length) {
46+
String block = paddedText.substring(i, Math.min(i + key.length, paddedText.length()));
47+
encrypted.append(permuteBlock(block, key));
48+
}
49+
50+
return encrypted.toString();
51+
}
52+
53+
/**
54+
* Decrypts the given ciphertext using the permutation cipher with the specified key.
55+
*
56+
* @param ciphertext the text to decrypt
57+
* @param key the permutation key (array of integers representing positions)
58+
* @return the decrypted text
59+
* @throws IllegalArgumentException if the key is invalid
60+
*/
61+
public String decrypt(String ciphertext, int[] key) {
62+
validateKey(key);
63+
64+
if (ciphertext == null || ciphertext.isEmpty()) {
65+
return ciphertext;
66+
}
67+
68+
// Create the inverse permutation
69+
int[] inverseKey = createInverseKey(key);
70+
71+
StringBuilder decrypted = new StringBuilder();
72+
73+
// Process text in blocks of key length
74+
for (int i = 0; i < ciphertext.length(); i += key.length) {
75+
String block = ciphertext.substring(i, Math.min(i + key.length, ciphertext.length()));
76+
decrypted.append(permuteBlock(block, inverseKey));
77+
}
78+
79+
// Remove padding characters from the end
80+
return removePadding(decrypted.toString());
81+
}
82+
/**
83+
* Validates that the permutation key is valid.
84+
* A valid key must contain all integers from 1 to n exactly once, where n is the key length.
85+
*
86+
* @param key the permutation key to validate
87+
* @throws IllegalArgumentException if the key is invalid
88+
*/
89+
private void validateKey(int[] key) {
90+
if (key == null || key.length == 0) {
91+
throw new IllegalArgumentException("Key cannot be null or empty");
92+
}
93+
94+
Set<Integer> keySet = new HashSet<>();
95+
for (int position : key) {
96+
if (position < 1 || position > key.length) {
97+
throw new IllegalArgumentException("Key must contain integers from 1 to " + key.length);
98+
}
99+
if (!keySet.add(position)) {
100+
throw new IllegalArgumentException("Key must contain each position exactly once");
101+
}
102+
}
103+
}
104+
105+
/**
106+
* Pads the text with padding characters to make its length divisible by the block size.
107+
*
108+
* @param text the text to pad
109+
* @param blockSize the size of each block
110+
* @return the padded text
111+
*/
112+
private String padText(String text, int blockSize) {
113+
int remainder = text.length() % blockSize;
114+
if (remainder == 0) {
115+
return text;
116+
}
117+
118+
int paddingNeeded = blockSize - remainder;
119+
StringBuilder padded = new StringBuilder(text);
120+
for (int i = 0; i < paddingNeeded; i++) {
121+
padded.append(PADDING_CHAR);
122+
}
123+
124+
return padded.toString();
125+
}
126+
/**
127+
* Applies the permutation to a single block of text.
128+
*
129+
* @param block the block to permute
130+
* @param key the permutation key
131+
* @return the permuted block
132+
*/
133+
private String permuteBlock(String block, int[] key) {
134+
if (block.length() != key.length) {
135+
// Handle case where block is shorter than key (shouldn't happen with proper padding)
136+
block = padText(block, key.length);
137+
}
138+
139+
char[] result = new char[key.length];
140+
char[] blockChars = block.toCharArray();
141+
142+
for (int i = 0; i < key.length; i++) {
143+
// Key positions are 1-based, so subtract 1 for 0-based array indexing
144+
result[i] = blockChars[key[i] - 1];
145+
}
146+
147+
return new String(result);
148+
}
149+
150+
/**
151+
* Creates the inverse permutation key for decryption.
152+
*
153+
* @param key the original permutation key
154+
* @return the inverse key
155+
*/
156+
private int[] createInverseKey(int[] key) {
157+
int[] inverse = new int[key.length];
158+
159+
for (int i = 0; i < key.length; i++) {
160+
// The inverse key maps each position to where it should go
161+
inverse[key[i] - 1] = i + 1;
162+
}
163+
164+
return inverse;
165+
}
166+
167+
/**
168+
* Removes padding characters from the end of the decrypted text.
169+
*
170+
* @param text the text to remove padding from
171+
* @return the text without padding
172+
*/
173+
private String removePadding(String text) {
174+
if (text.isEmpty()) {
175+
return text;
176+
}
177+
178+
int i = text.length() - 1;
179+
while (i >= 0 && text.charAt(i) == PADDING_CHAR) {
180+
i--;
181+
}
182+
183+
return text.substring(0, i + 1);
184+
}
185+
186+
/**
187+
* Gets the padding character used by this cipher.
188+
*
189+
* @return the padding character
190+
*/
191+
public char getPaddingChar() {
192+
return PADDING_CHAR;
193+
}
194+
}

0 commit comments

Comments
 (0)