Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@
* [BresenhamLine](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/BresenhamLine.java)
* [ConvexHull](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/ConvexHull.java)
* [GrahamScan](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/GrahamScan.java)
* [MidpointEllipse](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/MidpointEllipse.java)
* [Point](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/Point.java)
* graph
* [StronglyConnectedComponentOptimized](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java)
Expand Down Expand Up @@ -967,6 +968,7 @@
* [BresenhamLineTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java)
* [ConvexHullTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java)
* [GrahamScanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java)
* [MidpointEllipseTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/MidpointEllipseTest.java)
* graph
* [StronglyConnectedComponentOptimizedTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java)
* greedyalgorithms
Expand Down
58 changes: 56 additions & 2 deletions src/main/java/com/thealgorithms/ciphers/XORCipher.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,46 @@
import java.util.HexFormat;

/**
* A simple implementation of XOR cipher that, given a key, allows to encrypt and decrypt a plaintext.
* A simple implementation of the XOR cipher that allows both encryption and decryption
* using a given key. This cipher works by applying the XOR bitwise operation between
* the bytes of the input text and the corresponding bytes of the key (repeating the key
* if necessary).
*
* @author <a href="https://github.com/lcsjunior">lcsjunior</a>
* Usage:
* - Encryption: Converts plaintext into a hexadecimal-encoded ciphertext.
* - Decryption: Converts the hexadecimal ciphertext back into plaintext.
*
* Characteristics:
* - Symmetric: The same key is used for both encryption and decryption.
* - Simple but vulnerable: XOR encryption is insecure for real-world cryptography,
* especially when the same key is reused.
*
* Example:
* Plaintext: "Hello!"
* Key: "key"
* Encrypted: "27090c03120b"
* Decrypted: "Hello!"
*
* Reference: <a href="https://en.wikipedia.org/wiki/XOR_cipher">XOR Cipher - Wikipedia</a>
*
* @author <a href="https://github.com/lcsjunior">lcsjunior</a>
*/
public final class XORCipher {

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

private XORCipher() {
}

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

/**
* Encrypts the given plaintext using the XOR cipher with the specified key.
* The result is a hexadecimal-encoded string representing the ciphertext.
*
* @param plainText The input plaintext to encrypt.
* @param key The encryption key.
* @throws IllegalArgumentException if the key is empty.
* @return A hexadecimal string representing the encrypted text.
*/
public static String encrypt(final String plainText, final String key) {
if (key.isEmpty()) {
throw new IllegalArgumentException("Key must not be empty");
}

byte[] plainTextBytes = plainText.getBytes(CS_DEFAULT);
byte[] keyBytes = key.getBytes(CS_DEFAULT);
byte[] xorResult = xor(plainTextBytes, keyBytes);
return HexFormat.of().formatHex(xorResult);
}

/**
* Decrypts the given ciphertext (in hexadecimal format) using the XOR cipher
* with the specified key. The result is the original plaintext.
*
* @param cipherText The hexadecimal string representing the encrypted text.
* @param key The decryption key (must be the same as the encryption key).
* @throws IllegalArgumentException if the key is empty.
* @return The decrypted plaintext.
*/
public static String decrypt(final String cipherText, final String key) {
if (key.isEmpty()) {
throw new IllegalArgumentException("Key must not be empty");
}

byte[] cipherBytes = HexFormat.of().parseHex(cipherText);
byte[] keyBytes = key.getBytes(CS_DEFAULT);
byte[] xorResult = xor(cipherBytes, keyBytes);
Expand Down
77 changes: 64 additions & 13 deletions src/test/java/com/thealgorithms/ciphers/XORCipherTest.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,85 @@
package com.thealgorithms.ciphers;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

class XORCipherTest {

@Test
void xorEncryptTest() {
// given
void xorEncryptDecryptTest() {
String plaintext = "My t&xt th@t will be ençrypted...";
String key = "My ç&cret key!";

// when
String cipherText = XORCipher.encrypt(plaintext, key);
String decryptedText = XORCipher.decrypt(cipherText, key);

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

@Test
void xorDecryptTest() {
// given
String cipherText = "000000b7815e1752111c601f450e48211500a1c206061ca6d35212150d4429570eed";
String key = "My ç&cret key!";
void testEmptyPlaintext() {
String plaintext = "";
String key = "anykey";

String cipherText = XORCipher.encrypt(plaintext, key);
String decryptedText = XORCipher.decrypt(cipherText, key);

assertEquals("", cipherText);
assertEquals("", decryptedText);
}

@Test
void testEmptyKey() {
String plaintext = "Hello World!";
String key = "";

assertThrows(IllegalArgumentException.class, () -> XORCipher.encrypt(plaintext, key));
assertThrows(IllegalArgumentException.class, () -> XORCipher.decrypt(plaintext, key));
}

@Test
void testShortKey() {
String plaintext = "Short message";
String key = "k";

String cipherText = XORCipher.encrypt(plaintext, key);
String decryptedText = XORCipher.decrypt(cipherText, key);

assertEquals(plaintext, decryptedText);
}

// when
String plainText = XORCipher.decrypt(cipherText, key);
@Test
void testNonASCIICharacters() {
String plaintext = "こんにちは世界"; // "Hello World" in Japanese (Konichiwa Sekai)
String key = "key";

String cipherText = XORCipher.encrypt(plaintext, key);
String decryptedText = XORCipher.decrypt(cipherText, key);

assertEquals(plaintext, decryptedText);
}

@Test
void testSameKeyAndPlaintext() {
String plaintext = "samekey";
String key = "samekey";

String cipherText = XORCipher.encrypt(plaintext, key);
String decryptedText = XORCipher.decrypt(cipherText, key);

assertEquals(plaintext, decryptedText);
}

@Test
void testLongPlaintextShortKey() {
String plaintext = "This is a long plaintext message.";
String key = "key";

String cipherText = XORCipher.encrypt(plaintext, key);
String decryptedText = XORCipher.decrypt(cipherText, key);

// then
assertEquals("My t&xt th@t will be ençrypted...", plainText);
assertEquals(plaintext, decryptedText);
}
}