Introduction to the One-Time Pad
Math, F*ck Yeah

Introduction to the One-Time Pad

Allie G April 19, 2026 21 min read

One-Time Pad Cryptography: The Ultimate in Secure Communication

Cryptography is the art of encoding and decoding information to keep things private, and it plays a huge role in making sure our communication stays safe. Out of all the different encryption methods, there’s one that stands out for being incredibly simple, elegant, and most importantly, unbreakable: the One-Time Pad (OTP). Despite being around for over a century, the one-time pad is still considered a gold standard for secure communication. Let’s break down why that’s the case with this introduction to one-time pads.

What is a One-Time Pad?

A One-Time Pad is an encryption method that uses a secret key to scramble a message. You randomly generate the key. The key is the same length as the message, and it’s only used once. Because of this, it’s theoretically impossible to crack, if the key is truly random and kept secret.

Here’s how it works in a nutshell:

  1. Generate a Key: Create a completely random key. It’s the same length as the message you want to encrypt.
  2. Encrypt the Message: Combine the plaintext (the message) with the key using a special math formula. Like the XOR operation. It essentially scrambles the message so it’s unreadable to anyone who doesn’t have the key.
  3. Decrypt the Message: To decrypt, the receiver uses the same key to reverse the process and reveal the original message.

Why is the One-Time Pad So Secure?

The reason OTP is often called “unbreakable” comes down to a few things:

  • Key Length: The key must be as long as the message you’re sending. If it’s shorter or reused, someone can crack it.
  • Key Randomness: The key has to be totally random. If there’s any predictability, someone might be able to guess it.
  • Key Secrecy: Both the sender and receiver must keep the key a secret. If someone intercepts the key, they can decrypt the message.
  • Key Use: Use the key only once. Reusing a key weakens the encryption and can lead to patterns that hackers can exploit.

Experts consider the One-Time Pad theoretically impossible to break when the user meets the above conditions. There’s no pattern or predictability in the encrypted message. So a hacker can’t figure out what the original message was without the key.

A Simple Example of the One-Time Pad

Now let’s walk through a simple example to see how it works. This example uses an alphabet of 26 characters.

Message (Plaintext): HELLO

Key: XMCKL

First, we convert the letters into numbers (A=0, B=1, C=2, … Z=25):

  • HELLO = [7, 4, 11, 11, 14]
  • XMCKL = [23, 12, 2, 10, 11]

Now we encrypt the message by adding each number of the plaintext to the key, and then taking the result modulo 26 (to keep things within the alphabet range):

  • H + X = (7 + 23) % 26 = 4 → E
  • E + M = (4 + 12) % 26 = 16 → Q
  • L + C = (11 + 2) % 26 = 13 → N
  • L + K = (11 + 10) % 26 = 21 → V
  • O + L = (14 + 11) % 26 = 25 → Z

Ciphertext: EQNVZ

And to decrypt, the receiver uses the same key and subtracts the key values from the ciphertext:

  • E – X = (4 – 23) % 26 = 7 → H
  • Q – M = (16 – 12) % 26 = 4 → E
  • N – C = (13 – 2) % 26 = 11 → L
  • V – K = (21 – 10) % 26 = 11 → L
  • Z – L = (25 – 11) % 26 = 14 → O

Decrypted Message: HELLO

As you can see, with the correct key, it’s possible to perfectly recover the original message.

The Catch: Challenges of the One-Time Pad

While the One-Time Pad is super secure, it has some pretty big downsides when it comes to practical use:

  1. Key Distribution: The biggest challenge with OTP is getting the key to the receiver. Since the key has to be as long as the message, and it must remain secret, securely delivering the key can be tricky.
  2. Key Storage: Both the sender and receiver need to securely store the key. You cannot read the message, with no key, so don’t lose it. If someone obtains the key, they can read the message.
  3. Key Management: Use the key only once, meaning you’d need a brand new, random key for every message. If you’re sending a lot of messages, keeping track of all those keys can be a real hassle.
  4. True Randomness: Generating a truly random key is harder than it sounds. Most systems rely on pseudo-random number generators. These aren’t perfect and if someone knows how they work, they can crack the message.

Is the One-Time Pad in Use Today?

Despite these practical challenges, some very specific situations where absolute security is required still use the One-Time Pad. A few examples include:

  • Military and Intelligence: The military and intelligence agencies use the One-Time Pad for ultra-secure communications. It’s a go-to method when the stakes are extremely high and there can be no compromise on security.
  • Quantum Key Distribution: Some modern cryptographic methods, like Quantum Key Distribution (QKD), use principles similar to the One-Time Pad. These take advantage of quantum mechanics to ensure unbreakable encryption.

An Example One-Time Pad Coding Example

I developed the following code in Java11, using streams to create the texts and cipher texts as needed. The alphabet used is 43 characters instead of the 26 used in the earlier example. This allows spaces, periods, and other special characters as needed. The following Java code uses an API interface.

Java
package otpapi;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class OneTimePad {
	
	// Suppose Alice wishes to send the message hello to Bob. Assume two pads of paper containing identical 
	// random sequences of letters were somehow previously produced and securely issued to both. Alice chooses 
	// the appropriate unused page from the pad. The way to do this is normally arranged for in advance, 
	// as for instance "use the 12th sheet on 1 May", or "use the next available sheet for the next message".

	// The material on the selected sheet is the key for this message. Each letter from the pad will be 
	// combined in a predetermined way with one letter of the message. (It is common, but not required, 
	// to assign each letter a numerical value, e.g., a is 0, b is 1, and so on.)

	// this example, the technique is to combine the key and the message using modular addition, not 
	// unlike the Vigenère cipher. The numerical values of corresponding message and key letters are added 
	// together, modulo 26. So, if key material begins with XMCKL and the message is hello, then the coding 
	// would be done as follows:
	// [NOTE - instead of adding a random number, one can XOR a random byte, and read in the characters as 
	//         an array of bytes.
	
  //       h       e       l       l       o  message
  //    7 (h)   4 (e)  11 (l)  11 (l)  14 (o) message
  // + 23 (X)  12 (M)   2 (C)  10 (K)  11 (L) key
  // = 30      16      13      21      25     message + key  (send this instead of cipher text)
  // =  4 (E)  16 (Q)  13 (N)  21 (V)  25 (Z) (message + key) mod alphabet_size
  //       E       Q       N       V       Z  → ciphertext
	
	// If a number is larger than 25, then the remainder after subtraction of 26 is taken in modular arithmetic 
	// fashion. This simply means that if the computations "go past" Z, the sequence starts again at A.

	// The ciphertext to be sent to Bob is thus EQNVZ. Bob uses the matching key page and the same process, 
	// but in reverse, to obtain the plaintext. Here the key is subtracted from the ciphertext, again using 
	// modular arithmetic:
	
  //        E       Q       N       V       Z  ciphertext
  //     4 (E)  16 (Q)  13 (N)  21 (V)  25 (Z) ciphertext
  //   −23 (X)  12 (M)   2 (C)  10 (K)  11 (L) key
  // = −19       4      11      11      14     ciphertext – key
  // =   7 (h)   4 (e)  11 (l)  11 (l)  14 (o) ciphertext – key (mod alphabet_size)
  //       h       e       l       l       o  → message
	
	// As long as the key is perfectly random and is never repeated, the One time pad is perfectly secure.
	// The above is a traditional way to do a OTP cipher. That is what we will be using here, so the output 
	// will all be numbers.
	
	// Our example alphabet.
	private String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 ./'-:,"; // our 43 char alphabet
	
	// A map between the alphabet characters and their numerical representation.
  // private HashMap<Integer, String> map = new HashMap<>();
	
	/**
	 * Initialize with the default alphabet.
	 */
	public OneTimePad() {
	}
	
	/**
	 * Initialize with the selected alphabet.
	 * @param alphabet
	 */
	public OneTimePad(String alphabet) {
		this.alphabet = alphabet;
	}
	
	/**
	 * Get the current alphabet.
	 * @return A string containing the current alphabet.
	 */
	public String getAlphabet() {
		return this.alphabet;
	}

	/**
	 * Set the current alphabet.
	 * @param alphabet A string containing the desired alphabet.
	 */
	public void setAlphabet(String alphabet) {
		this.alphabet = alphabet;
	}

	/**
	 * Create a key with a given size using a pseudo random number generator. This can be
	 * broken with a long enough message or enough collected messages. This function is here
	 * to support testing. To ensure that the one time pad is unbreakable, use a manually
	 * generated key string of integers ( from 0 to alphabet size -1) or perhaps keyboard
	 * spacing time or something truly random instead of this function.
	 * @param keySize An int containing the size of the desired key.
	 * @return An array of ints containing the generated key.
	 */
	public List<Integer> createPseudoRandomKey(int keySize) {
		Random r = new Random();
		int min = 1;
		int max = this.alphabet.length();
		System.out.println("keySize="+keySize);
		
		// TODO change to use a seed to generate the random values with.
		List<Integer> key = Stream.generate(() -> (r.nextInt(max-min) + min))
				                   .limit(keySize)		
	    			               .collect(Collectors.toList());
		return key;
	}
	
	/**
	 * Convert a string into it's integer representation from our alphabet.
	 * @param inputMsg The input message to convert.
	 * @return The integer encoding of the message.
	 */	
	public List<Integer> convertMsg2Ints (String inputMsg) {
		List<Integer> msgInts = new ArrayList<Integer>();
		Stream.iterate(0, i-> i<inputMsg.length(),i-> i+1)
		      .forEach(i-> msgInts.add(this.alphabet.indexOf(inputMsg.substring(i, i+1))));
		return msgInts;
	}
	
	/**
	 * Encrypt the message.
	 * @param inputMsg The array of ints containing the message mapped to ints.
	 * @param key The array of ints containing the key.
	 * @return An array of ints containing the encrypted message.
	 */
	public List<Integer> encrypt (List<Integer> inputMsg, List<Integer> key) {
		// Add the key to the message mod alphabet size.
		List<Integer> addedBytes = new ArrayList<Integer>();
		Stream.iterate(0, i-> i<inputMsg.size(), i-> i+1) 
		      .forEach(i-> addedBytes.add(Math.floorMod((inputMsg.get(i) + key.get(i)), this.alphabet.length())));
		return addedBytes;
	}
	
	/**
	 * Decrypt the message.
	 * @param encryptedMsg An array of ints containing the encrypted message.
	 * @param key An array of ints containing the key.
	 * @return An array of ints which can be mapped back to message characters.
	 */
	public List<Integer> decrypt (List<Integer> encryptedMsg, List<Integer> key) {
		// Subtract the key mod alphabet size.
		List<Integer> subBytes = new ArrayList<Integer>();
		Stream.iterate(0, i-> i<encryptedMsg.size(), i-> i+1)
		      .forEach(i-> subBytes.add(Math.floorMod((encryptedMsg.get(i) - key.get(i)), this.alphabet.length())));
		return subBytes;
	}
	
	/**
	 * Map the message back to its alphabet.
	 * @param msg An array of ints that map to alphabet characters.
	 * @return The String containing the message.
	 */
	public String convertMsg2String (List<Integer> msg) {
		StringBuffer outputMsg = new StringBuffer();
		Stream.iterate(0, i-> i<msg.size(), i-> i+1)
    	      .forEach(i-> outputMsg.append(this.alphabet.substring(msg.get(i), msg.get(i)+1)));    
		return outputMsg.toString();
	}
	
	/**
	 * Pretty print the encrypted message into 5 character groups.
	 * @param encryptedMsg An array of ints containing the encrypted message.
	 * @return A string containing the encrypted message grouped into 5 
	 *         character groups separated by a blank and 0 filled at the end.
	 */
	public String prettyPrint (List<Integer> encryptedMsg) {
		// Create a string of the encrypted numbers, then separate into groups of 5.
		StringBuffer stringBuf = new StringBuffer();
		Stream.iterate(0, i-> i<encryptedMsg.size(), i-> i+1)
		      .forEach(i-> stringBuf.append(String.format("%02d", encryptedMsg.get(i))));
				
		// Take the string buffer and add in spaces after every 5 characters.
		// insert the first blank at 5, then loop to find the last blank and insert 5 positions later
		stringBuf.insert(5, " ");
		boolean complete = false;
		while (!complete) {
			if ((stringBuf.lastIndexOf(" ") + 6) < stringBuf.length()) {
			     stringBuf.insert(stringBuf.lastIndexOf(" ") + 6, " ");
			}  
			else {
				// Determine the number of 0's to append.
				// TODO Test when the diff is 0.
				int diff = (stringBuf.lastIndexOf(" ") + 6) - stringBuf.length();
				for (int j=0; j<diff; j++) {
					stringBuf.append("0");
				}
				complete = true;
			}    
		} 
		return stringBuf.toString();
	}

}

The following Java code is a simple test program that tests the functionality of our one-time pad interface. First, the message is converted from a string to an array of integers, then the test code creates a key for the test. The message is then encrypted with the key and saved. The encrypted message is then decrypted with the same key. Finally, the encrypted message is pretty-printed into groups of 5 characters until the entire message is printed. This is similar to how number stations broadcast these messages.

This code example, however, does not protect the message stored in the computer’s memory and possibly swapped out on disk. Also, the key generated in the example is a pseudorandom number and if used over time, someone can break the encryption. Instead of a pseudorandom number, one can use dice or something to generate random numbers. If one wants perfect security, manually encrypt and decrypt the messages with a truly random key. Keep the key secret and only transmit the encrypted message.

Java
package otpapi;

import java.util.List;

public class OTP_Test {
	
	/**
	 * This class tests each use case for the API. There may be a few more test cases needed.
	 * May use JUnit later.
	 */
	public void testOTP() {	
		
		// Create the test message. We are using a 42 character alphabet to get spaces, periods, 
		// numbers and a couple of special characters.
		OneTimePad otp = new OneTimePad();
		String testMsg = "1, 2, 3 ... THIS IS A SIMPLE TEST MESSAGE TO TEST OUT THE ONE TIME PAD.";
		
		// Convert the test message to an array of ints, may use bytes later;
		List<Integer> msgInts = otp.convertMsg2Ints(testMsg);
		
		// Display msgBytes
		System.out.println("msgBytes:");
		for (int i=0; i<msgInts.size() ; i++) {
			System.out.println("message letter " + testMsg.substring(i, i+1) + " = " + msgInts.get(i));
		}
		
		// Key Creation
    List<Integer> keyChars = otp.createPseudoRandomKey(msgInts.size());
    String prettyKey = otp.prettyPrint(keyChars);
		System.out.println("Pretty printed key: ");
		System.out.println("     " + prettyKey);
		
    // Encrypt
    List<Integer> addedInts = otp.encrypt(msgInts, keyChars);
		
		// Display the encrypted letters
		System.out.println("addedBytes:");
		for (int i=0; i<addedInts.size() ; i++) {
			System.out.println("added byte " + i + " = " + addedInts.get(i));
		}
		
		// Decrypt the message
		List<Integer> subInts = otp.decrypt(addedInts, keyChars);

		// Display the message.
		System.out.println("subBytes:");
		for (int i=0; i<subInts.size() ; i++) {
			System.out.println("subtracted byte " + i + " = " + subInts.get(i));
		}
		
		// Map the message back to a string.
		String outputMsg = otp.convertMsg2String (subInts);
		System.out.println("outputMsg: " + outputMsg);
		
		// Create a string of the encrypted numbers, then separate into groups of 5.
		String prettyMsg = otp.prettyPrint(addedInts);
		System.out.println("Pretty printed encrypted message: ");
		System.out.println("     " + prettyMsg);
	}
	
	public static void main(String[] args) {	
    OTP_Test prog = new OTP_Test();
    prog.testOTP();
	}

}

Conclusion

The One-Time Pad is a fascinating encryption method because it offers perfect security, theoretically. But, in practice, its limitations, like the difficulty of distributing and storing long, random keys, make it a less common choice for everyday use. But there is a modern use for the OTP, such as numbers stations. The message can be transmitted over shortwave to teams out in the field. Other methods of sending messages include social media or print media. That said, it remains an important concept in the world of cryptography and has inspired more modern secure communication methods.

While we may not use it to protect our emails or texts, the One-Time Pad is a great example of how encryption can, provide 100% secure communication. In today’s world, privacy is more important than ever. It reminds us that, with the right tools and the right conditions, total security is possible.

More Reading

One-time pad – Wikipedia

One-Time Pad (OTP)

8.2 The One-Time Pad and Perfect Secrecy

Leave a Comment