One-time pad encryption

One-time pad encryption is a theoretically perfect method for encrypting and decoding secret messages. The message is combined character-by-character by modular arithmetic with a "one-time key" which is at least as long as the message. For example, if the first character of the message is "E" (5th letter of the alphabet) and the first character of the key is "L" (12th letter of the alphabet), the first character of the cipher will be: (5+12)="Q". The one-time key is a uniformly random sequence of characters held by both the sender and receiver. Pads must be easy to conceal or destroy. Check out this website for a photo of a Russian one-time pad.

Encrypted Message

One-time pad encryption is "perfect" in the sense that as long as the key is truly random (and kept secret), the cipher gives absolutely no information about the original message, except for its maximum length. Given the cipher (but not the key), one can generate a possible key to transform the cipher into any arbitrary (intelligible) message. However, as the name suggests, each key may be used only once. If a key is used twice, the key can be broken relatively easy by making some basic assumptions about the underlying message (natural language, letter frequency, etc).

Since one-time pad ciphers are completely secure without access to the key, messages can be transmitted in the open. It has been speculated that so-called "numbers stations" transmitting across the shortwave band use one-time pad cyphers. Numbers Stations were the subject of a recent episode of the incredible and thought-provoking independent radio program 99% Invisible. Perfect data cryptography allows these stations to broadcast seemingly random streams of numbers out to secret agents, real or imagined, across the globe.

If you find yourself involved in a clandestine operation, perhaps you will find my Python script useful. All you need is a text file with your one-time keys (a single file can hold as many keys as you like, see sample for details). After specifying the padfile and which key to use, the program accepts the cipher or plaintext via STDIN. Run the program with the "-h" command line option for help and examples. The script uses a 38 character cipher (A-Z,0-9,=,/) where "=" is used in place of a space (" ") and "/" is used in place of a period ("."). Any other characters will be ignored and stripped from the message.

Python script

#!/usr/bin/env python

import sys
import argparse

#################################
# Parse command line arguments
#################################

parser = argparse.ArgumentParser(description='''Encrypt or decrypt text using a one-time pad cipher.

EXAMPLES:
  ./padcipher.py -p pad -k key -t plaintext.txt > cipher.txt
  ./padcipher.py -p pad -k key < plaintext.txt > cipher.txt
  cat cipher.txt | ./padcipher.py -p pad -k key -d > plaintext.txt''',epilog='If no input is supplied you can enter your message directly on the command line. Press RETURN then CTRL+D when done.',formatter_class=argparse.RawDescriptionHelpFormatter)

parser.add_argument('-p','--padfile', help='file containing keys (one-time pad)',
                    required=True);

parser.add_argument('-k','--keyname', help='name of the specific key to use',
                    required=True);

parser.add_argument('-d','--decrypt', action='store_const', dest='direction',const='d',
                    default='e', help='encrypt the input text (default: encrypt)')

parser.add_argument('-t','--text', help='file containing the text to be either encrypted or decrypted (the plaintext or the cipher)')

args = parser.parse_args()

# Try to load the key file
pf = open(args.padfile)

key = ""
foundKey = False

while not foundKey:

    line = pf.readline()

    if not line:
        print "ERROR: Pad not found in key file"
        pf.close()
        sys.exit(1)

    if line[0] == '#' and line[1:].strip() == args.keyname:
        foundKey = True
        while True:

            line = pf.readline()
            if line and line[0] != "#":
                key = key + line.strip().replace(' ', '')
            else:
                break

pf.close()


# Check to see if a text file was specified
if type(args.text) is str:
    t = open(args.text)

else:
    t = sys.stdin
#    print "Enter your message (Press RETURN then CTRL+D when done):"

inText = t.read().strip()
t.close()

# Check if the message is too long
if len(inText) > len(key):
    print "ERROR: Message is longer than key"
    sys.exit(0)


charMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=/"

# Preprocess text
inText = inText.upper()

if args.direction == 'e':
    # Convert spaces to '=' and periods to '/'
    inText = inText.replace(' ','=')
    inText = inText.replace('.','/')
else:
    inText = inText.replace(' ','')


# Apply one-time pad cipher
outText = ""
i = 0

if args.direction == 'e':
    sgn = 1
else:
    sgn = -1


for c in inText:
    if c in charMap:
        outCharNum = (charMap.index(c) + sgn*charMap.index(key[i])) % len(charMap)
        outText += charMap[outCharNum]
        i += 1

# Postprocess text
if args.direction == 'd':
    # Convert '=' to space and '/' to period
    outText = outText.replace('=',' ')
    outText = outText.replace('/','.')

print outText

Sample key file:

#K1
=K2KV 3POH3 OUQR1
E/FGS C00SD B7QMW
Y8UD= O/671 QKHNO
JSW49 ZZGRU 8EG00
TKETS WPYAH 0TJEP
R8S5K THVPO LQWBN
XN1MK 3OB0T YUFNZ
NA=X9 M1ILO 00/E/
/31G1 7U0A1 4FNOT

#K2
YC359 /U9KX 8X6Y7
V3PD6 H2D7S 6ZYYZ
44HG8 5UPKN =ETUK
534WU QJ1BT E2X58
SLLQR 307KD SPMDC
5PJ4G AYJYB AFK0L
8BKE2 N8VLY KG5V=
7I2U= 5JBJ6 4597O
R1WBN XJ0M8 1S9PY

#K3
1IPI7 42LSU DYUI1
ZGU35 ME59A DGPOZ
OVNHP VT8W4 =HP6B
2IS12 M7E9Q 9E269
TKW3E HPTTF GVCTF
OXSGZ JQU9M B2REU
7=7W9 F55VS YNW51
LHMZ6 KG524 MURYE
=SBK= LWNJB XC4JM