Skip to content

Commit 4e46002

Browse files
authored
Enhance docs, add more tests in XORCipher (#5900)
1 parent 03777f8 commit 4e46002

File tree

3 files changed

+122
-15
lines changed

3 files changed

+122
-15
lines changed

DIRECTORY.md

+2
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@
597597
* slidingwindow
598598
* [LongestSubstringWithoutRepeatingCharacters](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java)
599599
* [MaxSumKSizeSubarray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java)
600+
* [MinSumKSizeSubarray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarray.java)
600601
* sorts
601602
* [AdaptiveMergeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java)
602603
* [BeadSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BeadSort.java)
@@ -1217,6 +1218,7 @@
12171218
* slidingwindow
12181219
* [LongestSubstringWithoutRepeatingCharactersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java)
12191220
* [MaxSumKSizeSubarrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java)
1221+
* [MinSumKSizeSubarrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarrayTest.java)
12201222
* sorts
12211223
* [AdaptiveMergeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java)
12221224
* [BeadSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BeadSortTest.java)

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

+56-2
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,46 @@
55
import java.util.HexFormat;
66

77
/**
8-
* A simple implementation of XOR cipher that, given a key, allows to encrypt and decrypt a plaintext.
8+
* A simple implementation of the XOR cipher that allows both encryption and decryption
9+
* using a given key. This cipher works by applying the XOR bitwise operation between
10+
* the bytes of the input text and the corresponding bytes of the key (repeating the key
11+
* if necessary).
912
*
10-
* @author <a href="https://github.com/lcsjunior">lcsjunior</a>
13+
* Usage:
14+
* - Encryption: Converts plaintext into a hexadecimal-encoded ciphertext.
15+
* - Decryption: Converts the hexadecimal ciphertext back into plaintext.
16+
*
17+
* Characteristics:
18+
* - Symmetric: The same key is used for both encryption and decryption.
19+
* - Simple but vulnerable: XOR encryption is insecure for real-world cryptography,
20+
* especially when the same key is reused.
21+
*
22+
* Example:
23+
* Plaintext: "Hello!"
24+
* Key: "key"
25+
* Encrypted: "27090c03120b"
26+
* Decrypted: "Hello!"
1127
*
28+
* Reference: <a href="https://en.wikipedia.org/wiki/XOR_cipher">XOR Cipher - Wikipedia</a>
29+
*
30+
* @author <a href="https://github.com/lcsjunior">lcsjunior</a>
1231
*/
1332
public final class XORCipher {
1433

34+
// Default character encoding for string conversion
1535
private static final Charset CS_DEFAULT = StandardCharsets.UTF_8;
1636

1737
private XORCipher() {
1838
}
1939

40+
/**
41+
* Applies the XOR operation between the input bytes and the key bytes.
42+
* If the key is shorter than the input, it wraps around (cyclically).
43+
*
44+
* @param inputBytes The input byte array (plaintext or ciphertext).
45+
* @param keyBytes The key byte array used for XOR operation.
46+
* @return A new byte array containing the XOR result.
47+
*/
2048
public static byte[] xor(final byte[] inputBytes, final byte[] keyBytes) {
2149
byte[] outputBytes = new byte[inputBytes.length];
2250
for (int i = 0; i < inputBytes.length; ++i) {
@@ -25,14 +53,40 @@ public static byte[] xor(final byte[] inputBytes, final byte[] keyBytes) {
2553
return outputBytes;
2654
}
2755

56+
/**
57+
* Encrypts the given plaintext using the XOR cipher with the specified key.
58+
* The result is a hexadecimal-encoded string representing the ciphertext.
59+
*
60+
* @param plainText The input plaintext to encrypt.
61+
* @param key The encryption key.
62+
* @throws IllegalArgumentException if the key is empty.
63+
* @return A hexadecimal string representing the encrypted text.
64+
*/
2865
public static String encrypt(final String plainText, final String key) {
66+
if (key.isEmpty()) {
67+
throw new IllegalArgumentException("Key must not be empty");
68+
}
69+
2970
byte[] plainTextBytes = plainText.getBytes(CS_DEFAULT);
3071
byte[] keyBytes = key.getBytes(CS_DEFAULT);
3172
byte[] xorResult = xor(plainTextBytes, keyBytes);
3273
return HexFormat.of().formatHex(xorResult);
3374
}
3475

76+
/**
77+
* Decrypts the given ciphertext (in hexadecimal format) using the XOR cipher
78+
* with the specified key. The result is the original plaintext.
79+
*
80+
* @param cipherText The hexadecimal string representing the encrypted text.
81+
* @param key The decryption key (must be the same as the encryption key).
82+
* @throws IllegalArgumentException if the key is empty.
83+
* @return The decrypted plaintext.
84+
*/
3585
public static String decrypt(final String cipherText, final String key) {
86+
if (key.isEmpty()) {
87+
throw new IllegalArgumentException("Key must not be empty");
88+
}
89+
3690
byte[] cipherBytes = HexFormat.of().parseHex(cipherText);
3791
byte[] keyBytes = key.getBytes(CS_DEFAULT);
3892
byte[] xorResult = xor(cipherBytes, keyBytes);
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,85 @@
11
package com.thealgorithms.ciphers;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
45

56
import org.junit.jupiter.api.Test;
67

78
class XORCipherTest {
89

910
@Test
10-
void xorEncryptTest() {
11-
// given
11+
void xorEncryptDecryptTest() {
1212
String plaintext = "My t&xt th@t will be ençrypted...";
1313
String key = "My ç&cret key!";
1414

15-
// when
1615
String cipherText = XORCipher.encrypt(plaintext, key);
16+
String decryptedText = XORCipher.decrypt(cipherText, key);
1717

18-
// then
19-
assertEquals("000000b7815e1752111c601f450e48211500a1c206061ca6d35212150d4429570eed", cipherText);
18+
assertEquals("My t&xt th@t will be ençrypted...", decryptedText);
2019
}
2120

2221
@Test
23-
void xorDecryptTest() {
24-
// given
25-
String cipherText = "000000b7815e1752111c601f450e48211500a1c206061ca6d35212150d4429570eed";
26-
String key = "My ç&cret key!";
22+
void testEmptyPlaintext() {
23+
String plaintext = "";
24+
String key = "anykey";
25+
26+
String cipherText = XORCipher.encrypt(plaintext, key);
27+
String decryptedText = XORCipher.decrypt(cipherText, key);
28+
29+
assertEquals("", cipherText);
30+
assertEquals("", decryptedText);
31+
}
32+
33+
@Test
34+
void testEmptyKey() {
35+
String plaintext = "Hello World!";
36+
String key = "";
37+
38+
assertThrows(IllegalArgumentException.class, () -> XORCipher.encrypt(plaintext, key));
39+
assertThrows(IllegalArgumentException.class, () -> XORCipher.decrypt(plaintext, key));
40+
}
41+
42+
@Test
43+
void testShortKey() {
44+
String plaintext = "Short message";
45+
String key = "k";
46+
47+
String cipherText = XORCipher.encrypt(plaintext, key);
48+
String decryptedText = XORCipher.decrypt(cipherText, key);
49+
50+
assertEquals(plaintext, decryptedText);
51+
}
2752

28-
// when
29-
String plainText = XORCipher.decrypt(cipherText, key);
53+
@Test
54+
void testNonASCIICharacters() {
55+
String plaintext = "こんにちは世界"; // "Hello World" in Japanese (Konichiwa Sekai)
56+
String key = "key";
57+
58+
String cipherText = XORCipher.encrypt(plaintext, key);
59+
String decryptedText = XORCipher.decrypt(cipherText, key);
60+
61+
assertEquals(plaintext, decryptedText);
62+
}
63+
64+
@Test
65+
void testSameKeyAndPlaintext() {
66+
String plaintext = "samekey";
67+
String key = "samekey";
68+
69+
String cipherText = XORCipher.encrypt(plaintext, key);
70+
String decryptedText = XORCipher.decrypt(cipherText, key);
71+
72+
assertEquals(plaintext, decryptedText);
73+
}
74+
75+
@Test
76+
void testLongPlaintextShortKey() {
77+
String plaintext = "This is a long plaintext message.";
78+
String key = "key";
79+
80+
String cipherText = XORCipher.encrypt(plaintext, key);
81+
String decryptedText = XORCipher.decrypt(cipherText, key);
3082

31-
// then
32-
assertEquals("My t&xt th@t will be ençrypted...", plainText);
83+
assertEquals(plaintext, decryptedText);
3384
}
3485
}

0 commit comments

Comments
 (0)