Skip to content

Commit f8397bf

Browse files
authored
Add ADFGVX Cipher (#5631)
1 parent 8722598 commit f8397bf

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.thealgorithms.ciphers;
2+
3+
import java.util.Arrays;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
/**
7+
* The ADFGVX cipher is a historically significant cipher used by
8+
* the German Army during World War I. It is a fractionating transposition
9+
* cipher that combines a Polybius square substitution with a columnar
10+
* transposition. It's named after the six letters (A, D, F, G, V, X)
11+
* that it uses in its substitution process.
12+
* https://en.wikipedia.org/wiki/ADFGVX_cipher
13+
*
14+
* @author bennybebo
15+
*/
16+
public class ADFGVXCipher {
17+
18+
private static final char[] POLYBIUS_LETTERS = {'A', 'D', 'F', 'G', 'V', 'X'};
19+
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'}};
20+
private static final Map<String, Character> POLYBIUS_MAP = new HashMap<>();
21+
private static final Map<Character, String> REVERSE_POLYBIUS_MAP = new HashMap<>();
22+
23+
static {
24+
for (int i = 0; i < POLYBIUS_SQUARE.length; i++) {
25+
for (int j = 0; j < POLYBIUS_SQUARE[i].length; j++) {
26+
String key = "" + POLYBIUS_LETTERS[i] + POLYBIUS_LETTERS[j];
27+
POLYBIUS_MAP.put(key, POLYBIUS_SQUARE[i][j]);
28+
REVERSE_POLYBIUS_MAP.put(POLYBIUS_SQUARE[i][j], key);
29+
}
30+
}
31+
}
32+
33+
// Encrypts the plaintext using the ADFGVX cipher
34+
public String encrypt(String plaintext, String key) {
35+
plaintext = plaintext.toUpperCase().replaceAll("[^A-Z0-9]", "");
36+
StringBuilder fractionatedText = new StringBuilder();
37+
38+
// Step 1: Polybius square substitution
39+
for (char c : plaintext.toCharArray()) {
40+
fractionatedText.append(REVERSE_POLYBIUS_MAP.get(c));
41+
}
42+
43+
// Step 2: Columnar transposition
44+
return columnarTransposition(fractionatedText.toString(), key);
45+
}
46+
47+
// Decrypts the ciphertext using the ADFGVX cipher
48+
public String decrypt(String ciphertext, String key) {
49+
// Step 1: Reverse the columnar transposition
50+
String fractionatedText = reverseColumnarTransposition(ciphertext, key);
51+
52+
// Step 2: Polybius square substitution
53+
StringBuilder plaintext = new StringBuilder();
54+
for (int i = 0; i < fractionatedText.length(); i += 2) {
55+
String pair = fractionatedText.substring(i, i + 2);
56+
plaintext.append(POLYBIUS_MAP.get(pair));
57+
}
58+
59+
return plaintext.toString();
60+
}
61+
62+
private String columnarTransposition(String text, String key) {
63+
int numRows = (int) Math.ceil((double) text.length() / key.length());
64+
char[][] table = new char[numRows][key.length()];
65+
for (char[] row : table) {
66+
Arrays.fill(row, '_'); // Fill with underscores to handle empty cells
67+
}
68+
69+
// Fill the table row by row
70+
for (int i = 0; i < text.length(); i++) {
71+
table[i / key.length()][i % key.length()] = text.charAt(i);
72+
}
73+
74+
// Read columns based on the alphabetical order of the key
75+
StringBuilder ciphertext = new StringBuilder();
76+
char[] sortedKey = key.toCharArray();
77+
Arrays.sort(sortedKey);
78+
79+
for (char keyChar : sortedKey) {
80+
int column = key.indexOf(keyChar);
81+
for (char[] row : table) {
82+
if (row[column] != '_') {
83+
ciphertext.append(row[column]);
84+
}
85+
}
86+
}
87+
88+
return ciphertext.toString();
89+
}
90+
91+
private String reverseColumnarTransposition(String ciphertext, String key) {
92+
int numRows = (int) Math.ceil((double) ciphertext.length() / key.length());
93+
char[][] table = new char[numRows][key.length()];
94+
95+
char[] sortedKey = key.toCharArray();
96+
Arrays.sort(sortedKey);
97+
98+
int index = 0;
99+
// Fill the table column by column according to the sorted key order
100+
for (char keyChar : sortedKey) {
101+
int column = key.indexOf(keyChar);
102+
for (int row = 0; row < numRows; row++) {
103+
if (index < ciphertext.length()) {
104+
table[row][column] = ciphertext.charAt(index++);
105+
} else {
106+
table[row][column] = '_'; // Fill empty cells with an underscore
107+
}
108+
}
109+
}
110+
111+
// Read the table row by row to get the fractionated text
112+
StringBuilder fractionatedText = new StringBuilder();
113+
for (char[] row : table) {
114+
for (char cell : row) {
115+
if (cell != '_') {
116+
fractionatedText.append(cell);
117+
}
118+
}
119+
}
120+
121+
return fractionatedText.toString();
122+
}
123+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.thealgorithms.ciphers;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
class ADFGVXCipherTest {
8+
9+
ADFGVXCipher adfgvxCipher = new ADFGVXCipher();
10+
11+
@Test
12+
void adfgvxCipherEncryptTest() {
13+
// given
14+
String message = "attack at 1200am"; // Plaintext message
15+
String keyword = "PRIVACY";
16+
17+
// when
18+
String cipherText = adfgvxCipher.encrypt(message, keyword);
19+
20+
// then
21+
assertEquals("DGDDDAGDDGAFADDFDADVDVFAADVX", cipherText);
22+
}
23+
24+
@Test
25+
void adfgvxCipherDecryptTest() {
26+
// given
27+
String cipherText = "DGDDDAGDDGAFADDFDADVDVFAADVX"; // Ciphertext message
28+
String keyword = "PRIVACY";
29+
30+
// when
31+
String plainText = adfgvxCipher.decrypt(cipherText, keyword);
32+
33+
// then
34+
assertEquals("ATTACKAT1200AM", plainText);
35+
}
36+
}

0 commit comments

Comments
 (0)