Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
78 changes: 78 additions & 0 deletions src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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.
* <p>
* <strong>Important:</strong> For the OTP cipher to be perfectly secure, the
* following conditions must be met:
* <ul>
* <li>The key must be truly random.</li>
* <li>The key must be at least as long as the message.</li>
* <li>The key must never be reused (used only once).</li>
* <li>The key must be kept completely secret.</li>
* </ul>
* <p>
* Example usage:
* <pre>{@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"
* }</pre>
*
* @see <a href="https://en.wikipedia.org/wiki/One-time_pad">One-Time Pad Cipher - Wikipedia</a>
* @author <a href="https://github.com/newtownboy">newtownboy</a>
*/
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;
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading