Skip to content

Commit 8a8a1aa

Browse files
committed
feat: Add ADFGVXCipher new algorithm with Junit tests
1 parent e499d3b commit 8a8a1aa

File tree

2 files changed

+89
-37
lines changed

2 files changed

+89
-37
lines changed

src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,38 @@
33
import java.util.Arrays;
44
import java.util.HashMap;
55
import java.util.Map;
6+
67
/**
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
8+
* The ADFGVX cipher is a fractionating transposition cipher that was used by
9+
* the German Army during World War I. It combines a **Polybius square substitution**
10+
* with a **columnar transposition** to enhance encryption strength.
11+
* <p>
12+
* The name "ADFGVX" refers to the six letters (A, D, F, G, V, X) used as row and
13+
* column labels in the Polybius square. This cipher was designed to secure
14+
* communication and create complex, hard-to-break ciphertexts.
15+
* <p>
16+
* Learn more: <a href="https://en.wikipedia.org/wiki/ADFGVX_cipher">ADFGVX Cipher - Wikipedia</a>.
17+
* <p>
18+
* Example usage:
19+
* <pre>
20+
* ADFGVXCipher cipher = new ADFGVXCipher();
21+
* String encrypted = cipher.encrypt("attack at 1200am", "PRIVACY");
22+
* String decrypted = cipher.decrypt(encrypted, "PRIVACY");
23+
* </pre>
1324
*
1425
* @author bennybebo
1526
*/
1627
public class ADFGVXCipher {
1728

29+
// Constants used in the Polybius square
1830
private static final char[] POLYBIUS_LETTERS = {'A', 'D', 'F', 'G', 'V', 'X'};
1931
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'}};
32+
33+
// Maps for fast substitution lookups
2034
private static final Map<String, Character> POLYBIUS_MAP = new HashMap<>();
2135
private static final Map<Character, String> REVERSE_POLYBIUS_MAP = new HashMap<>();
2236

37+
// Static block to initialize the lookup tables from the Polybius square
2338
static {
2439
for (int i = 0; i < POLYBIUS_SQUARE.length; i++) {
2540
for (int j = 0; j < POLYBIUS_SQUARE[i].length; j++) {
@@ -30,26 +45,41 @@ public class ADFGVXCipher {
3045
}
3146
}
3247

33-
// Encrypts the plaintext using the ADFGVX cipher
48+
/**
49+
* Encrypts a given plaintext using the ADFGVX cipher with the provided keyword.
50+
* Steps:
51+
* 1. Substitute each letter in the plaintext with a pair of ADFGVX letters.
52+
* 2. Perform a columnar transposition on the fractionated text using the keyword.
53+
*
54+
* @param plaintext The message to be encrypted (can contain letters and digits).
55+
* @param key The keyword for columnar transposition.
56+
* @return The encrypted message as ciphertext.
57+
*/
3458
public String encrypt(String plaintext, String key) {
35-
plaintext = plaintext.toUpperCase().replaceAll("[^A-Z0-9]", "");
59+
plaintext = plaintext.toUpperCase().replaceAll("[^A-Z0-9]", ""); // Sanitize input
3660
StringBuilder fractionatedText = new StringBuilder();
3761

38-
// Step 1: Polybius square substitution
3962
for (char c : plaintext.toCharArray()) {
4063
fractionatedText.append(REVERSE_POLYBIUS_MAP.get(c));
4164
}
4265

43-
// Step 2: Columnar transposition
4466
return columnarTransposition(fractionatedText.toString(), key);
4567
}
4668

47-
// Decrypts the ciphertext using the ADFGVX cipher
69+
/**
70+
* Decrypts a given ciphertext using the ADFGVX cipher with the provided keyword.
71+
* Steps:
72+
* 1. Reverse the columnar transposition performed during encryption.
73+
* 2. Substitute each pair of ADFGVX letters with the corresponding plaintext letter.
74+
* The resulting text is the decrypted message.
75+
*
76+
* @param ciphertext The encrypted message.
77+
* @param key The keyword used during encryption.
78+
* @return The decrypted plaintext message.
79+
*/
4880
public String decrypt(String ciphertext, String key) {
49-
// Step 1: Reverse the columnar transposition
5081
String fractionatedText = reverseColumnarTransposition(ciphertext, key);
5182

52-
// Step 2: Polybius square substitution
5383
StringBuilder plaintext = new StringBuilder();
5484
for (int i = 0; i < fractionatedText.length(); i += 2) {
5585
String pair = fractionatedText.substring(i, i + 2);
@@ -59,14 +89,19 @@ public String decrypt(String ciphertext, String key) {
5989
return plaintext.toString();
6090
}
6191

92+
/**
93+
* Helper method: Performs columnar transposition during encryption
94+
*
95+
* @param text The fractionated text to be transposed
96+
* @param key The keyword for columnar transposition
97+
* @return The transposed text
98+
*/
6299
private String columnarTransposition(String text, String key) {
63100
int numRows = (int) Math.ceil((double) text.length() / key.length());
64101
char[][] table = new char[numRows][key.length()];
65-
for (char[] row : table) {
66-
Arrays.fill(row, '_'); // Fill with underscores to handle empty cells
67-
}
102+
for (char[] row : table) Arrays.fill(row, '_'); // Fill empty cells with underscores
68103

69-
// Fill the table row by row
104+
// Populate the table row by row
70105
for (int i = 0; i < text.length(); i++) {
71106
table[i / key.length()][i % key.length()] = text.charAt(i);
72107
}
@@ -88,6 +123,13 @@ private String columnarTransposition(String text, String key) {
88123
return ciphertext.toString();
89124
}
90125

126+
/**
127+
* Helper method: Reverses the columnar transposition during decryption
128+
*
129+
* @param ciphertext The transposed text to be reversed
130+
* @param key The keyword used during encryption
131+
* @return The reversed text
132+
*/
91133
private String reverseColumnarTransposition(String ciphertext, String key) {
92134
int numRows = (int) Math.ceil((double) ciphertext.length() / key.length());
93135
char[][] table = new char[numRows][key.length()];
@@ -96,19 +138,19 @@ private String reverseColumnarTransposition(String ciphertext, String key) {
96138
Arrays.sort(sortedKey);
97139

98140
int index = 0;
99-
// Fill the table column by column according to the sorted key order
141+
// Populate the table column by column according to the sorted key
100142
for (char keyChar : sortedKey) {
101143
int column = key.indexOf(keyChar);
102144
for (int row = 0; row < numRows; row++) {
103145
if (index < ciphertext.length()) {
104146
table[row][column] = ciphertext.charAt(index++);
105147
} else {
106-
table[row][column] = '_'; // Fill empty cells with an underscore
148+
table[row][column] = '_';
107149
}
108150
}
109151
}
110152

111-
// Read the table row by row to get the fractionated text
153+
// Read the table row by row to reconstruct the fractionated text
112154
StringBuilder fractionatedText = new StringBuilder();
113155
for (char[] row : table) {
114156
for (char cell : row) {

src/test/java/com/thealgorithms/ciphers/ADFGVXCipherTest.java

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,41 @@
66

77
class ADFGVXCipherTest {
88

9-
ADFGVXCipher adfgvxCipher = new ADFGVXCipher();
9+
private final ADFGVXCipher adfgvxCipher = new ADFGVXCipher();
1010

1111
@Test
12-
void adfgvxCipherEncryptTest() {
13-
// given
14-
String message = "attack at 1200am"; // Plaintext message
15-
String keyword = "PRIVACY";
12+
void testEncrypt() {
13+
String message = "attack at 1200am";
14+
String key = "PRIVACY";
1615

17-
// when
18-
String cipherText = adfgvxCipher.encrypt(message, keyword);
16+
String encrypted = adfgvxCipher.encrypt(message, key);
17+
assertEquals("DGDDDAGDDGAFADDFDADVDVFAADVX", encrypted);
18+
}
19+
20+
@Test
21+
void testDecrypt() {
22+
String encrypted = "DGDDDAGDDGAFADDFDADVDVFAADVX";
23+
String key = "PRIVACY";
1924

20-
// then
21-
assertEquals("DGDDDAGDDGAFADDFDADVDVFAADVX", cipherText);
25+
String decrypted = adfgvxCipher.decrypt(encrypted, key);
26+
assertEquals("ATTACKAT1200AM", decrypted);
2227
}
2328

2429
@Test
25-
void adfgvxCipherDecryptTest() {
26-
// given
27-
String cipherText = "DGDDDAGDDGAFADDFDADVDVFAADVX"; // Ciphertext message
28-
String keyword = "PRIVACY";
30+
void testEmptyInput() {
31+
String encrypted = adfgvxCipher.encrypt("", "PRIVACY");
32+
String decrypted = adfgvxCipher.decrypt("", "PRIVACY");
33+
assertEquals("", encrypted);
34+
assertEquals("", decrypted);
35+
}
2936

30-
// when
31-
String plainText = adfgvxCipher.decrypt(cipherText, keyword);
37+
@Test
38+
void testShortKey() {
39+
String message = "TESTING";
40+
String key = "A";
3241

33-
// then
34-
assertEquals("ATTACKAT1200AM", plainText);
42+
String encrypted = adfgvxCipher.encrypt(message, key);
43+
String decrypted = adfgvxCipher.decrypt(encrypted, key);
44+
assertEquals("TESTING", decrypted);
3545
}
3646
}

0 commit comments

Comments
 (0)