-
Notifications
You must be signed in to change notification settings - Fork 19.9k
/
Copy pathADFGVXCipher.java
167 lines (146 loc) · 6.36 KB
/
ADFGVXCipher.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package com.thealgorithms.ciphers;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* The ADFGVX cipher is a fractionating transposition cipher that was used by
* the German Army during World War I. It combines a **Polybius square substitution**
* with a **columnar transposition** to enhance encryption strength.
* <p>
* The name "ADFGVX" refers to the six letters (A, D, F, G, V, X) used as row and
* column labels in the Polybius square. This cipher was designed to secure
* communication and create complex, hard-to-break ciphertexts.
* <p>
* Learn more: <a href="https://en.wikipedia.org/wiki/ADFGVX_cipher">ADFGVX Cipher - Wikipedia</a>.
* <p>
* Example usage:
* <pre>
* ADFGVXCipher cipher = new ADFGVXCipher();
* String encrypted = cipher.encrypt("attack at 1200am", "PRIVACY");
* String decrypted = cipher.decrypt(encrypted, "PRIVACY");
* </pre>
*
* @author bennybebo
*/
public class ADFGVXCipher {
// Constants used in the Polybius square
private static final char[] POLYBIUS_LETTERS = {'A', 'D', 'F', 'G', 'V', 'X'};
private static final char[][] POLYBIUS_SQUARE = {{'N', 'A', '1', 'C', '3', 'H'}, {'8', 'T', 'B', '2', 'O', 'M'}, {'E', '5', 'W', 'R', 'P', 'D'}, {'4', 'F', '6', 'G', '7', 'I'}, {'9', 'J', '0', 'K', 'L', 'Q'}, {'S', 'U', 'V', 'X', 'Y', 'Z'}};
// Maps for fast substitution lookups
private static final Map<String, Character> POLYBIUS_MAP = new HashMap<>();
private static final Map<Character, String> REVERSE_POLYBIUS_MAP = new HashMap<>();
// Static block to initialize the lookup tables from the Polybius square
static {
for (int i = 0; i < POLYBIUS_SQUARE.length; i++) {
for (int j = 0; j < POLYBIUS_SQUARE[i].length; j++) {
String key = "" + POLYBIUS_LETTERS[i] + POLYBIUS_LETTERS[j];
POLYBIUS_MAP.put(key, POLYBIUS_SQUARE[i][j]);
REVERSE_POLYBIUS_MAP.put(POLYBIUS_SQUARE[i][j], key);
}
}
}
/**
* Encrypts a given plaintext using the ADFGVX cipher with the provided keyword.
* Steps:
* 1. Substitute each letter in the plaintext with a pair of ADFGVX letters.
* 2. Perform a columnar transposition on the fractionated text using the keyword.
*
* @param plaintext The message to be encrypted (can contain letters and digits).
* @param key The keyword for columnar transposition.
* @return The encrypted message as ciphertext.
*/
public String encrypt(String plaintext, String key) {
plaintext = plaintext.toUpperCase().replaceAll("[^A-Z0-9]", ""); // Sanitize input
StringBuilder fractionatedText = new StringBuilder();
for (char c : plaintext.toCharArray()) {
fractionatedText.append(REVERSE_POLYBIUS_MAP.get(c));
}
return columnarTransposition(fractionatedText.toString(), key);
}
/**
* Decrypts a given ciphertext using the ADFGVX cipher with the provided keyword.
* Steps:
* 1. Reverse the columnar transposition performed during encryption.
* 2. Substitute each pair of ADFGVX letters with the corresponding plaintext letter.
* The resulting text is the decrypted message.
*
* @param ciphertext The encrypted message.
* @param key The keyword used during encryption.
* @return The decrypted plaintext message.
*/
public String decrypt(String ciphertext, String key) {
String fractionatedText = reverseColumnarTransposition(ciphertext, key);
StringBuilder plaintext = new StringBuilder();
for (int i = 0; i < fractionatedText.length(); i += 2) {
String pair = fractionatedText.substring(i, i + 2);
plaintext.append(POLYBIUS_MAP.get(pair));
}
return plaintext.toString();
}
/**
* Helper method: Performs columnar transposition during encryption
*
* @param text The fractionated text to be transposed
* @param key The keyword for columnar transposition
* @return The transposed text
*/
private String columnarTransposition(String text, String key) {
int numRows = (int) Math.ceil((double) text.length() / key.length());
char[][] table = new char[numRows][key.length()];
for (char[] row : table) { // Fill empty cells with underscores
Arrays.fill(row, '_');
}
// Populate the table row by row
for (int i = 0; i < text.length(); i++) {
table[i / key.length()][i % key.length()] = text.charAt(i);
}
// Read columns based on the alphabetical order of the key
StringBuilder ciphertext = new StringBuilder();
char[] sortedKey = key.toCharArray();
Arrays.sort(sortedKey);
for (char keyChar : sortedKey) {
int column = key.indexOf(keyChar);
for (char[] row : table) {
if (row[column] != '_') {
ciphertext.append(row[column]);
}
}
}
return ciphertext.toString();
}
/**
* Helper method: Reverses the columnar transposition during decryption
*
* @param ciphertext The transposed text to be reversed
* @param key The keyword used during encryption
* @return The reversed text
*/
private String reverseColumnarTransposition(String ciphertext, String key) {
int numRows = (int) Math.ceil((double) ciphertext.length() / key.length());
char[][] table = new char[numRows][key.length()];
char[] sortedKey = key.toCharArray();
Arrays.sort(sortedKey);
int index = 0;
// Populate the table column by column according to the sorted key
for (char keyChar : sortedKey) {
int column = key.indexOf(keyChar);
for (int row = 0; row < numRows; row++) {
if (index < ciphertext.length()) {
table[row][column] = ciphertext.charAt(index++);
} else {
table[row][column] = '_';
}
}
}
// Read the table row by row to reconstruct the fractionated text
StringBuilder fractionatedText = new StringBuilder();
for (char[] row : table) {
for (char cell : row) {
if (cell != '_') {
fractionatedText.append(cell);
}
}
}
return fractionatedText.toString();
}
}