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