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