Skip to content

Commit 8d2cdb2

Browse files
feat: implement One-Time Pad cipher (#6941) (#7096)
* feat: implement One-Time Pad cipher (#6941) * style: format OneTimePadCipher with clang-format
1 parent 14c0b08 commit 8d2cdb2

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.thealgorithms.ciphers;
2+
3+
import java.security.SecureRandom;
4+
import java.util.Objects;
5+
6+
/**
7+
* One-Time Pad (OTP) cipher implementation.
8+
*
9+
* <p>The One-Time Pad is information-theoretically secure if:
10+
* <ul>
11+
* <li>The key is truly random.</li>
12+
* <li>The key length is at least as long as the plaintext.</li>
13+
* <li>The key is used only once and kept secret.</li>
14+
* </ul>
15+
*
16+
* <p>This implementation is for <b>educational purposes only</b> and should not be
17+
* used in production systems.
18+
*/
19+
public final class OneTimePadCipher {
20+
21+
private static final SecureRandom RANDOM = new SecureRandom();
22+
23+
private OneTimePadCipher() {
24+
// utility class
25+
}
26+
27+
/**
28+
* Generates a random key of the given length in bytes.
29+
*
30+
* @param length the length of the key in bytes, must be non-negative
31+
* @return a new random key
32+
* @throws IllegalArgumentException if length is negative
33+
*/
34+
public static byte[] generateKey(int length) {
35+
if (length < 0) {
36+
throw new IllegalArgumentException("length must be non-negative");
37+
}
38+
byte[] key = new byte[length];
39+
RANDOM.nextBytes(key);
40+
return key;
41+
}
42+
43+
/**
44+
* Encrypts the given plaintext bytes using the provided key.
45+
* <p>The key length must be exactly the same as the plaintext length.
46+
*
47+
* @param plaintext the plaintext bytes, must not be {@code null}
48+
* @param key the one-time pad key bytes, must not be {@code null}
49+
* @return the ciphertext bytes
50+
* @throws IllegalArgumentException if the key length does not match plaintext length
51+
* @throws NullPointerException if plaintext or key is {@code null}
52+
*/
53+
public static byte[] encrypt(byte[] plaintext, byte[] key) {
54+
validateInputs(plaintext, key);
55+
return xor(plaintext, key);
56+
}
57+
58+
/**
59+
* Decrypts the given ciphertext bytes using the provided key.
60+
* <p>For a One-Time Pad, decryption is identical to encryption:
61+
* {@code plaintext = ciphertext XOR key}.
62+
*
63+
* @param ciphertext the ciphertext bytes, must not be {@code null}
64+
* @param key the one-time pad key bytes, must not be {@code null}
65+
* @return the decrypted plaintext bytes
66+
* @throws IllegalArgumentException if the key length does not match ciphertext length
67+
* @throws NullPointerException if ciphertext or key is {@code null}
68+
*/
69+
public static byte[] decrypt(byte[] ciphertext, byte[] key) {
70+
validateInputs(ciphertext, key);
71+
return xor(ciphertext, key);
72+
}
73+
74+
private static void validateInputs(byte[] input, byte[] key) {
75+
Objects.requireNonNull(input, "input must not be null");
76+
Objects.requireNonNull(key, "key must not be null");
77+
if (input.length != key.length) {
78+
throw new IllegalArgumentException("Key length must match input length");
79+
}
80+
}
81+
82+
private static byte[] xor(byte[] data, byte[] key) {
83+
byte[] result = new byte[data.length];
84+
for (int i = 0; i < data.length; i++) {
85+
result[i] = (byte) (data[i] ^ key[i]);
86+
}
87+
return result;
88+
}
89+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.thealgorithms.ciphers;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
7+
import java.nio.charset.StandardCharsets;
8+
import org.junit.jupiter.api.Test;
9+
10+
class OneTimePadCipherTest {
11+
12+
@Test
13+
void encryptAndDecryptWithRandomKeyRestoresPlaintext() {
14+
String plaintext = "The quick brown fox jumps over the lazy dog.";
15+
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
16+
17+
byte[] key = OneTimePadCipher.generateKey(plaintextBytes.length);
18+
19+
byte[] ciphertext = OneTimePadCipher.encrypt(plaintextBytes, key);
20+
byte[] decrypted = OneTimePadCipher.decrypt(ciphertext, key);
21+
22+
assertArrayEquals(plaintextBytes, decrypted);
23+
assertEquals(plaintext, new String(decrypted, StandardCharsets.UTF_8));
24+
}
25+
26+
@Test
27+
void generateKeyWithNegativeLengthThrowsException() {
28+
assertThrows(IllegalArgumentException.class, () -> OneTimePadCipher.generateKey(-1));
29+
}
30+
31+
@Test
32+
void encryptWithMismatchedKeyLengthThrowsException() {
33+
byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
34+
byte[] shortKey = OneTimePadCipher.generateKey(2);
35+
36+
assertThrows(IllegalArgumentException.class, () -> OneTimePadCipher.encrypt(data, shortKey));
37+
}
38+
39+
@Test
40+
void decryptWithMismatchedKeyLengthThrowsException() {
41+
byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
42+
byte[] key = OneTimePadCipher.generateKey(data.length);
43+
byte[] ciphertext = OneTimePadCipher.encrypt(data, key);
44+
45+
byte[] wrongSizedKey = OneTimePadCipher.generateKey(data.length + 1);
46+
47+
assertThrows(IllegalArgumentException.class, () -> OneTimePadCipher.decrypt(ciphertext, wrongSizedKey));
48+
}
49+
}

0 commit comments

Comments
 (0)