Understand the Security of One Time Pad and how to Implement it in Python

What is a One Time Pad?

A One Time Pad is a information-theoretical secure encryption function. But what does that mean?

On a high level the One Time Pad is a simple XOR function that takes the input and xor’s that with a key-stream.

One Time Pad illustrated.
One Time Pad illustrated.

Encryption and decryption are identical.

  • Encryption: Takes the plaintext and the key-stream and xor’s it to get the cipher text.
  • Decryption: Takes the cipher text and the (same) key-stream and xor’s it to get the plaintext.

Some requirements of the One Time Pad are.

  • Key-stream should only be used once.
  • Key-stream should only be known by the sender and the receiver.
  • Key-stream should be generated by true randomness

Hence, the requirement are only on the key-stream, which obviously is the only input to the algorithm.

The beauty of the algorithm is the simplicity.

Understand the Security of the One Time Pad

The One Time Pad is information-theoretical secure.

That means, that even if the evil adversary had infinite computing power, it could not break it.

One Time Pad is unbreakable - it is information-theoretical secure.
One Time Pad is unbreakable – it is information-theoretical secure.

The simples way to understand why that is the case is the following. If an adversary catches an encrypted message, which has length, say 10 characters. It can decrypt to any message of length 10.

The reason is, that the key-stream can be anything and is a long as the message itself. That implies, that the plaintext can be possible message of 10 characters.

If the key-stream is unknown, then the cipher text can decrypt to any message.
If the key-stream is unknown, then the cipher text can decrypt to any message.

Implementation in Python

Obviously, we have a dilemma. We cannot generate a key like that in Python.

The actual implementation of the One Time Pad is done by a simple xor.

def xor_bytes(key_stream, message):
    return bytes([key_stream[i] ^ message[i] for i in range(length)])

Of course, this requires that the key_stream and message have the same length.

It also leaves out the problem of where the key_stream comes from. The problem is, that you cannot create a key_stream with the required properties in Python.

Demonstrate the security in Python

If you were to receive a message encrypted by a One Time Pad, then for any guess of the plaintext, there is a matching key-stream to get it.

See the code for better understanding it.

def xor_bytes(key_stream, message):
    return bytes([key_stream[i] ^ message[i] for i in range(length)])


cipher
# cipher is the cipher text
# len(cipher) = 10

# If we guess that the plaintext is "DO ATTACK"
# Then the corresponding key_stream can be computes as follows
message = "DO ATTACK"
message = message.encode()
key_stream = xor_bytes(message, cipher)


# Similar, if we guess the plaintext is "NO ATTACK"
# Then the corresponding key_stream can be computes as follows
message = "NO ATTACK"
message = message.encode()
guess_key = xor_bytes(message, cipher)

Conclusion

While One Time Pads are ideal encryption system, they are not practical. The reason is, that there is no efficient way to generate and distribute a true random key-stream, which is only used once and not known by others than sender and receiver.

The Stream Cipher is often used, as a compromise for that. Examples of stream ciphers are A5/1.

How Caesar Cipher Teaches us the Most Valuable Lesson – Learn Kerckhoff’s Principle in 5 Steps with Python Code

What will we cover?

  • Understand the challenge to send a secret message
  • Understand the Caesar Cipher
  • How to create an implementation of that in Python
  • How to break the Caesar Cipher
  • Understand the importance of Kerckhoff’s Principle

Step 1: Understand the challenge to send a secret message

In cryptography you have three people involved in almost any scenario. We have Alice that wants to send a message to Bob. But Alice want to send it in a way, such that she ensures that Eve (the evil person) cannot understand it.

But let’s break with tradition and introduce an addition person, Mike. Mike is the messenger. Because we are back in the times of Caesar. Alice represent one of Caesar close generals that needs to send a message to the front lines of the army. Bob is in the front line and waits for a command from Alice. DO ATTACK or NO ATTACK.

Alice will use Mike, the messenger, to send that message to Bob.

Alice is of course afraid of that Eve, the evil enemy, will capture Mike along the way.

Of course, as Alice is smart, she knows that Mike should not understand the message he is delivering, and Eve should not be able to understand it as well. It should only add value to Bob, when Mike gives him the message.

That is the problem that Caesar wanted to solve with his cipher system.

Step 2: Understand the Caesar Cipher

Let’s do this a bit backwards.

You receive the message. BRX DUH DZHVRPH

That is pretty impossible to understand. But if you were told that this is the Caesar Cipher using the shift of 3 characters. Then maybe it makes sense.

As you can see, then green letters are the plaintext characters and the red letters are the encrypted cipher text letters. Hence, A will be a D. That is the letter A is shifted 3 characters down the row.

Reversing this, you see the the encrypted B, will map to the plaintext Y.

If you continue this process you will get.

That is a nice message to get.

Step 3: How to create an implementation of that in Python

Well, that is easy. There are many ways to do it. I will make use of the dictionary to make my life easy.

def generate_key(n):
    letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    key = {}
    cnt = 0
    for c in letters:
        key[ c] = letters[(cnt + n) % len(letters)]
        cnt += 1
    return key


def get_decryption_key(key):
    dkey = {}
    for c in key:
        dkey[key[ c]] = c
    return dkey

    
def encrypt(key, message):
    cipher = ""
    for c in message:
        if c in key:
            cipher += key[ c]
        else:
            cipher += c
    return cipher


# This is setting up your Caesar Cipher key
key = generate_key(3)
# Hmm... I guess this will print the key
print(key)
# This will encrypt the message you have chose with your key
message = "YOU ARE AWESOME"
cipher = encrypt(key, message)
# I guess we should print out your AWESOME message
print(cipher)

Step 4: How to break the Caesar Cipher

If you look at it like this. There is a flaw in the system. Can you see what?

Yes, of course you can. We are in the 2020ies and not back in the times of Caesar.

The key space is too small.

Breaking it basically takes the following code.

# this is us breaking the cipher
print(cipher)
for i in range(26):
    dkey = generate_key(i)
    message = encrypt(dkey, cipher)
    print(message)

You read the code correct. There are only 26 keys. That means, that even back in the days of Caesar this could be done in hand.

This leads us to the most valuable lesson in cryptography and most important principle.

Step 5: Understand the importance of Kerckhoff’s Principle

Let’s just recap what happened here.

Alice sent a message to Bob that Eve captured. Eve did not understand it.

But the reason why Eve did not understand it, was not because she did not have the key.

No, if she knew the algorithm.

Yes, if Eve knew the algorithm of Caesar Cipher, she would not need the secret key to break it.

This leads to the most important lesson in cryptography. Kerckhoff’s Principle.

Eve should not be able to break the ciphers even when she knows the cipher.

Kerckhoff’s Principle

That is seems counterintuitive, right? Yes, but think about it, if you system is secure against any attack even if you reveal your algorithm, then it would give you more confidence that it is secure.

You security should not be based on keeping the algorithm secret. No it should be based on the secret key.

Is that principle followed?

No.

Most government ciphers are kept secret.

Many secret encryption algorithms that leaked were broken.

This also includes the one used for mobile traffic in the old G2 network. A5/1 and the export version A5/2.

What is a Substitution Cipher and is it Secure?

What will you learn?

  • What is a Substitution Cipher?
  • How to implement the Substitution Cipher in Python
  • Understand the key space of Substitution Cipher
  • The weakness and how to break the Substitution Cipher

What is a Substitution Cipher

Imagine you receive this message: IQL WPV WCVJQEV

What does it mean?

You were told it is a Substitution Cipher, but how will that help you?

First of all, we need to understand what a Substitution Cipher is. Basically, it is just rearranging the characters. That is every time you write an A, you exchange that with, say, Q. And B with G. C with, hey, let’s keep the C.

See the mapping in the picture below.

Substitution Cipher Example
Substitution Cipher Example

That seems pretty solid. Right?

But can we figure out what your message means?

First, let’s try to implement a Substitution Cipher.

Implementing Substitution Cipher in Python

We will use the random library to generate random keys. We’ll get back to how many keys are there.

import random


def generate_key():
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    chars = list(alphabet)
    key = {}
    for c in alphabet:
        key = chars.pop(random.randint(0, len(chars) - 1))
    return key


def print_key(key):
    for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
        print(c, key)


def encrypt(key, message):
    cipher = ""
    for c in message:
        if c in key:
            cipher += key
        else:
            cipher += " "
    return cipher


def get_decrypt_key(key):
    dkey = {}
    for k in key:
        dkey[key[k]] = k
    return dkey


key = generate_key()
print(key)
cipher = encrypt(key, "YOU ARE AWESOME")
print(cipher)
dkey = get_decrypt_key(key)
message = encrypt(dkey, cipher)
print(message)

Well, let’s understand the key-space. How secure is the Substitution Cipher?

The key-space of the Substitution Cipher

Let’s examine how the keys are generated.

Substitution Cipher with key-space calculation
Substitution Cipher with key-space calculation

The first letter, A, can be mapped to 26 possibilities (including A itself). The next letter, B, can be mapped to 25 possibilities. The third letter, C, can be mapped to (difficult to guess) 24 possibilities. And so forth.

This generates a space of (read the large number in the picture above). That is right. It is more than 88 bits of security.

That is a lot.

88 bits of security? That is you need to try (more than) 309485009821345068724781056 possibilities.

A whole lot. And that, I promise you, should be considered secure.

But wait, Substitution Cipher is not used any more, why? Read on, and you will know in a moment.

The weakness of Substitution Cipher

If the underlying language is English, then you can make a simple frequency analysis of how often the letters occur on average in English.

It turns out to be quite simple.

letter_freq = {'a': 0.0817, 'b': 0.0150, 'c': 0.0278, 'd': 0.0425, 'e': 0.1270, 'f': 0.0223,
               'g': 0.0202, 'h': 0.0609, 'i': 0.0697, 'j': 0.0015, 'k': 0.0077, 'l': 0.0403,
               'm': 0.0241, 'n': 0.0675, 'o': 0.0751, 'p': 0.0193, 'q': 0.0010, 'r': 0.0599,
               's': 0.0633, 't': 0.0906, 'u': 0.0276, 'v': 0.0098, 'w': 0.0236, 'x': 0.0015,
               'y': 0.0197, 'z': 0.0007}

That is, the letter ‘a’ occurs 8.17% percent probability. ‘b’ with 1.5%. ‘c’ with 2.78%.

Hence, given the text: IQL WPV WCVJQEV

Well, we are out of luck, because it is too short to have any frequency analysis to have any significance.

But the following text.

lrvmnir bpr sumvbwvr jx bpr lmiwv yjeryrkbi jx qmbm wi
bpr xjvni mkd ymibrut jx irhx wi bpr riirkvr jx
ymbinlmtmipw utn qmumbr dj w ipmhh but bj rhnvwdmbr bpr
yjeryrkbi jx bpr qmbm mvvjudwko bj yt wkbrusurbmbwjk
lmird jk xjubt trmui jx ibndt
  wb wi kjb mk rmit bmiq bj rashmwk rmvp yjeryrkb mkd wbi
iwokwxwvmkvr mkd ijyr ynib urymwk nkrashmwkrd bj ower m
vjyshrbr rashmkmbwjk jkr cjnhd pmer bj lr fnmhwxwrd mkd
wkiswurd bj invp mk rabrkb bpmb pr vjnhd urmvp bpr ibmbr
jx rkhwopbrkrd ywkd vmsmlhr jx urvjokwgwko ijnkdhrii
ijnkd mkd ipmsrhrii ipmsr w dj kjb drry ytirhx bpr xwkmh
mnbpjuwbt lnb yt rasruwrkvr cwbp qmbm pmi hrxb kj djnlb
bpmb bpr xjhhjcwko wi bpr sujsru msshwvmbwjk mkd
wkbrusurbmbwjk w jxxru yt bprjuwri wk bpr pjsr bpmb bpr
riirkvr jx jqwkmcmk qmumbr cwhh urymwk wkbmvb

This has enough letters to make an analysis of the letters. Let’s try.

cipher = """lrvmnir bpr sumvbwvr jx bpr lmiwv yjeryrkbi jx qmbm wi
bpr xjvni mkd ymibrut jx irhx wi bpr riirkvr jx
ymbinlmtmipw utn qmumbr dj w ipmhh but bj rhnvwdmbr bpr
yjeryrkbi jx bpr qmbm mvvjudwko bj yt wkbrusurbmbwjk
lmird jk xjubt trmui jx ibndt
  wb wi kjb mk rmit bmiq bj rashmwk rmvp yjeryrkb mkd wbi
iwokwxwvmkvr mkd ijyr ynib urymwk nkrashmwkrd bj ower m
vjyshrbr rashmkmbwjk jkr cjnhd pmer bj lr fnmhwxwrd mkd
wkiswurd bj invp mk rabrkb bpmb pr vjnhd urmvp bpr ibmbr
jx rkhwopbrkrd ywkd vmsmlhr jx urvjokwgwko ijnkdhrii
ijnkd mkd ipmsrhrii ipmsr w dj kjb drry ytirhx bpr xwkmh
mnbpjuwbt lnb yt rasruwrkvr cwbp qmbm pmi hrxb kj djnlb
bpmb bpr xjhhjcwko wi bpr sujsru msshwvmbwjk mkd
wkbrusurbmbwjk w jxxru yt bprjuwri wk bpr pjsr bpmb bpr
riirkvr jx jqwkmcmk qmumbr cwhh urymwk wkbmvb"""

alphabet = "abcdefghijklmnopqrstuvwxyz"
freq = {}
for c in alphabet:
    freq = 0

cnt = 0
for c in cipher:
    if c in alphabet:
        freq += 1
        cnt += 1

for c in freq:
    freq = round(freq/cnt, 4)

print(freq)

Will give you the following output.

{'a': 0.0077, 'b': 0.1053, 'c': 0.0077, 'd': 0.0356, 'e': 0.0077, 'f': 0.0015, 'g': 0.0015, 'h': 0.0356, 'i': 0.0635, 'j': 0.0743, 'k': 0.0759, 'l': 0.0124, 'm': 0.096, 'n': 0.0263, 'o': 0.0108, 'p': 0.0464, 'q': 0.0108, 'r': 0.13, 's': 0.0263, 't': 0.0201, 'u': 0.0372, 'v': 0.0341, 'w': 0.0728, 'x': 0.031, 'y': 0.0294, 'z': 0.0}

This gives you some hints on how the letters are mapped.