Skip to content

Commit fffc89c

Browse files
committed
feat: implement One-Time Pad cipher (#6941)
- Added encryption and decryption functions using One-Time Pad - Included unit tests for edge cases and invalid inputs
1 parent d717ca4 commit fffc89c

File tree

2 files changed

+173
-0
lines changed

2 files changed

+173
-0
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.thealgorithms.ciphers;
2+
3+
import java.security.SecureRandom;
4+
5+
/**
6+
* The {@code OneTimePadCipher} class provides a simple implementation of the
7+
* One-Time Pad (OTP) cipher — a theoretically unbreakable symmetric encryption
8+
* technique when used correctly.
9+
* <p>
10+
* This cipher works by generating a truly random key of the same length as the
11+
* plaintext, and then performing a bitwise XOR (exclusive OR) operation between
12+
* the plaintext bytes and the key bytes. The resulting ciphertext can be
13+
* decrypted by applying the same XOR operation again using the same key.
14+
* <p>
15+
* <strong>Important:</strong> For the OTP cipher to be perfectly secure, the
16+
* following conditions must be met:
17+
* <ul>
18+
* <li>The key must be truly random.</li>
19+
* <li>The key must be at least as long as the message.</li>
20+
* <li>The key must never be reused (used only once).</li>
21+
* <li>The key must be kept completely secret.</li>
22+
* </ul>
23+
* <p>
24+
* Example usage:
25+
* <pre>{@code
26+
* String message = "HELLO";
27+
* byte[] plaintext = message.getBytes(StandardCharsets.UTF_8);
28+
* byte[] key = OneTimePadCipher.generateKey(plaintext.length);
29+
*
30+
* // Encrypt
31+
* byte[] ciphertext = OneTimePadCipher.applyOTP(plaintext, key);
32+
*
33+
* // Decrypt (same method)
34+
* byte[] decrypted = OneTimePadCipher.applyOTP(ciphertext, key);
35+
* System.out.println(new String(decrypted, StandardCharsets.UTF_8)); // "HELLO"
36+
* }</pre>
37+
*
38+
* @see <a href="https://en.wikipedia.org/wiki/One-time_pad">One-Time Pad Cipher - Wikipedia</a>
39+
* @author <a href="https://github.com/newtownboy">newtownboy</a>
40+
*/
41+
public final class OneTimePadCipher {
42+
private static final SecureRandom random = new SecureRandom();
43+
44+
private OneTimePadCipher() {
45+
}
46+
47+
/**
48+
* Generate a truly random key of the same length as the plaintext.
49+
*
50+
* @param length length of the key
51+
* @return random key as byte array
52+
*/
53+
public static byte[] generateKey(int length) {
54+
byte[] key = new byte[length];
55+
random.nextBytes(key);
56+
return key;
57+
}
58+
59+
/**
60+
* Encrypt or decrypt using XOR operation.
61+
* Since XOR is symmetric, the same method works for both.
62+
*
63+
* @param input plaintext or ciphertext
64+
* @param key key of the same length as input
65+
* @return result after XOR
66+
*/
67+
public static byte[] applyOTP(byte[] input, byte[] key) {
68+
if (input.length != key.length) {
69+
throw new IllegalArgumentException("Input and key must be the same length");
70+
}
71+
72+
byte[] output = new byte[input.length];
73+
for (int i = 0; i < input.length; i++) {
74+
output[i] = (byte) (input[i] ^ key[i]);
75+
}
76+
return output;
77+
}
78+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.thealgorithms.ciphers;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.Arrays;
6+
7+
import static org.junit.jupiter.api.Assertions.*;
8+
9+
class OneTimePadCipherTest {
10+
@Test
11+
void testGenerateKeyLength() {
12+
int length = 16;
13+
byte[] key = OneTimePadCipher.generateKey(length);
14+
assertNotNull(key, "Key should not be null");
15+
assertEquals(length, key.length, "Key length should match requested length");
16+
}
17+
18+
@Test
19+
void testEncryptDecrypt() {
20+
String plaintext = "Hello, OneTimePad!";
21+
byte[] input = plaintext.getBytes();
22+
23+
byte[] key = OneTimePadCipher.generateKey(input.length);
24+
25+
byte[] ciphertext = OneTimePadCipher.applyOTP(input, key);
26+
assertNotNull(ciphertext);
27+
assertNotEquals(plaintext, new String(ciphertext), "Ciphertext should differ from plaintext");
28+
29+
byte[] decrypted = OneTimePadCipher.applyOTP(ciphertext, key);
30+
assertEquals(plaintext, new String(decrypted), "Decrypted text should match original plaintext");
31+
}
32+
33+
@Test
34+
void testInvalidKeyLength() {
35+
byte[] input = "Hello, OneTimePad!".getBytes();
36+
byte[] wrongKey = OneTimePadCipher.generateKey(input.length + 1);
37+
38+
assertThrows(IllegalArgumentException.class, () -> {
39+
OneTimePadCipher.applyOTP(input, wrongKey);
40+
});
41+
}
42+
43+
@Test
44+
void testEmptyInputAndKey() {
45+
byte[] input = new byte[0];
46+
byte[] key = new byte[0];
47+
byte[] result = OneTimePadCipher.applyOTP(input, key);
48+
assertEquals(0, result.length, "Empty input should return empty output");
49+
}
50+
51+
@Test
52+
void testDeterministicXorProperty() {
53+
byte[] input = "XORTest".getBytes();
54+
byte[] key = OneTimePadCipher.generateKey(input.length);
55+
56+
byte[] once = OneTimePadCipher.applyOTP(input, key);
57+
byte[] twice = OneTimePadCipher.applyOTP(once, key);
58+
59+
assertArrayEquals(input, twice, "Applying OTP twice with same key should return original input");
60+
}
61+
62+
@Test
63+
void testRandomKeyUniqueness() {
64+
byte[] key1 = OneTimePadCipher.generateKey(32);
65+
byte[] key2 = OneTimePadCipher.generateKey(32);
66+
67+
assertFalse(Arrays.equals(key1, key2), "Two generated keys should not be identical");
68+
}
69+
70+
@Test
71+
void testUnicodeCharacters() {
72+
String plaintext = "Hello, OneTimePad!";
73+
byte[] input = plaintext.getBytes();
74+
75+
byte[] key = OneTimePadCipher.generateKey(input.length);
76+
byte[] ciphertext = OneTimePadCipher.applyOTP(input, key);
77+
byte[] decrypted = OneTimePadCipher.applyOTP(ciphertext, key);
78+
79+
assertEquals(plaintext, new String(decrypted), "Decrypted Unicode text should match original");
80+
}
81+
82+
@Test
83+
void testNullInputs() {
84+
byte[] validKey = OneTimePadCipher.generateKey(4);
85+
byte[] validInput = "Test".getBytes();
86+
87+
assertThrows(NullPointerException.class, () -> OneTimePadCipher.applyOTP(null, validKey));
88+
assertThrows(NullPointerException.class, () -> OneTimePadCipher.applyOTP(validInput, null));
89+
}
90+
91+
@Test
92+
void testNegativeKeyLength() {
93+
assertThrows(NegativeArraySizeException.class, () -> OneTimePadCipher.generateKey(-5));
94+
}
95+
}

0 commit comments

Comments
 (0)