diff --git a/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java b/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java new file mode 100644 index 000000000000..66d512c5553f --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java @@ -0,0 +1,78 @@ +package com.thealgorithms.ciphers; + +import java.security.SecureRandom; + +/** + * The {@code OneTimePadCipher} class provides a simple implementation of the + * One-Time Pad (OTP) cipher — a theoretically unbreakable symmetric encryption + * technique when used correctly. + *
+ * This cipher works by generating a truly random key of the same length as the + * plaintext, and then performing a bitwise XOR (exclusive OR) operation between + * the plaintext bytes and the key bytes. The resulting ciphertext can be + * decrypted by applying the same XOR operation again using the same key. + *
+ * Important: For the OTP cipher to be perfectly secure, the + * following conditions must be met: + *
+ * Example usage: + *
{@code
+ * String message = "HELLO";
+ * byte[] plaintext = message.getBytes(StandardCharsets.UTF_8);
+ * byte[] key = OneTimePadCipher.generateKey(plaintext.length);
+ *
+ * // Encrypt
+ * byte[] ciphertext = OneTimePadCipher.applyOTP(plaintext, key);
+ *
+ * // Decrypt (same method)
+ * byte[] decrypted = OneTimePadCipher.applyOTP(ciphertext, key);
+ * System.out.println(new String(decrypted, StandardCharsets.UTF_8)); // "HELLO"
+ * }
+ *
+ * @see One-Time Pad Cipher - Wikipedia
+ * @author newtownboy
+ */
+public final class OneTimePadCipher {
+ private static final SecureRandom random = new SecureRandom();
+
+ private OneTimePadCipher() {
+ }
+
+ /**
+ * Generate a truly random key of the same length as the plaintext.
+ *
+ * @param length length of the key
+ * @return random key as byte array
+ */
+ public static byte[] generateKey(int length) {
+ byte[] key = new byte[length];
+ random.nextBytes(key);
+ return key;
+ }
+
+ /**
+ * Encrypt or decrypt using XOR operation.
+ * Since XOR is symmetric, the same method works for both.
+ *
+ * @param input plaintext or ciphertext
+ * @param key key of the same length as input
+ * @return result after XOR
+ */
+ public static byte[] applyOTP(byte[] input, byte[] key) {
+ if (input.length != key.length) {
+ throw new IllegalArgumentException("Input and key must be the same length");
+ }
+
+ byte[] output = new byte[input.length];
+ for (int i = 0; i < input.length; i++) {
+ output[i] = (byte) (input[i] ^ key[i]);
+ }
+ return output;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.java b/src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.java
new file mode 100644
index 000000000000..1d797b8ceb6d
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.java
@@ -0,0 +1,95 @@
+package com.thealgorithms.ciphers;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class OneTimePadCipherTest {
+ @Test
+ void testGenerateKeyLength() {
+ int length = 16;
+ byte[] key = OneTimePadCipher.generateKey(length);
+ assertNotNull(key, "Key should not be null");
+ assertEquals(length, key.length, "Key length should match requested length");
+ }
+
+ @Test
+ void testEncryptDecrypt() {
+ String plaintext = "Hello, OneTimePad!";
+ byte[] input = plaintext.getBytes();
+
+ byte[] key = OneTimePadCipher.generateKey(input.length);
+
+ byte[] ciphertext = OneTimePadCipher.applyOTP(input, key);
+ assertNotNull(ciphertext);
+ assertNotEquals(plaintext, new String(ciphertext), "Ciphertext should differ from plaintext");
+
+ byte[] decrypted = OneTimePadCipher.applyOTP(ciphertext, key);
+ assertEquals(plaintext, new String(decrypted), "Decrypted text should match original plaintext");
+ }
+
+ @Test
+ void testInvalidKeyLength() {
+ byte[] input = "Hello, OneTimePad!".getBytes();
+ byte[] wrongKey = OneTimePadCipher.generateKey(input.length + 1);
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ OneTimePadCipher.applyOTP(input, wrongKey);
+ });
+ }
+
+ @Test
+ void testEmptyInputAndKey() {
+ byte[] input = new byte[0];
+ byte[] key = new byte[0];
+ byte[] result = OneTimePadCipher.applyOTP(input, key);
+ assertEquals(0, result.length, "Empty input should return empty output");
+ }
+
+ @Test
+ void testDeterministicXorProperty() {
+ byte[] input = "XORTest".getBytes();
+ byte[] key = OneTimePadCipher.generateKey(input.length);
+
+ byte[] once = OneTimePadCipher.applyOTP(input, key);
+ byte[] twice = OneTimePadCipher.applyOTP(once, key);
+
+ assertArrayEquals(input, twice, "Applying OTP twice with same key should return original input");
+ }
+
+ @Test
+ void testRandomKeyUniqueness() {
+ byte[] key1 = OneTimePadCipher.generateKey(32);
+ byte[] key2 = OneTimePadCipher.generateKey(32);
+
+ assertFalse(Arrays.equals(key1, key2), "Two generated keys should not be identical");
+ }
+
+ @Test
+ void testUnicodeCharacters() {
+ String plaintext = "Hello, OneTimePad!";
+ byte[] input = plaintext.getBytes();
+
+ byte[] key = OneTimePadCipher.generateKey(input.length);
+ byte[] ciphertext = OneTimePadCipher.applyOTP(input, key);
+ byte[] decrypted = OneTimePadCipher.applyOTP(ciphertext, key);
+
+ assertEquals(plaintext, new String(decrypted), "Decrypted Unicode text should match original");
+ }
+
+ @Test
+ void testNullInputs() {
+ byte[] validKey = OneTimePadCipher.generateKey(4);
+ byte[] validInput = "Test".getBytes();
+
+ assertThrows(NullPointerException.class, () -> OneTimePadCipher.applyOTP(null, validKey));
+ assertThrows(NullPointerException.class, () -> OneTimePadCipher.applyOTP(validInput, null));
+ }
+
+ @Test
+ void testNegativeKeyLength() {
+ assertThrows(NegativeArraySizeException.class, () -> OneTimePadCipher.generateKey(-5));
+ }
+}
\ No newline at end of file