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.
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.
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.
The Key value is the tells you which algorithm is used (SHA512) and how it is used (PBKDF2).
The entropy is the actual result of the validation algorithm determined by the key value . This “value” is not an encryption of the password, which means you cannot recover the password from that value, but you can validate if the password matches this value.
Confused? I know. But you will understand when we implement the solution
The number of iterations, here 45454, is the number of times the hash function is called. Also, why would you call the hash function multiple times? Follow along and you will see.
Finally, we have the salt value. That is to ensure that you cannot determine the password from the entropy value itself. This will also get explained with example below.
Step 3: Validating the password with Python
Before we explain the above, we need to be have Python do the check of the password.
We use the hashlib library to do all the work for us. It takes the algorithm (sha512), the password (Yes, I used the password ‘password’ in this example, you should not actually use that for anything you want to keep secret from the public), the salt and the number of iterations.
Now we are ready to explore the questions.
Why use a Hash value and not an encryption of the password?
If the password was encrypted, then an admin on your network would be able to decrypt it and misuse it.
Hence, to keep it safe from that, an iterated hash value of your password is used.
A hash function is a one-way function that can map any input to a fixed sized output. A hash function will have these important properties in regards to passwords.
It will always map the same input to the same output. Hence, your password will always be mapped to the same value.
A small change in the input will give a big change in output. Hence, if you change one character in the password (say, from ‘password’ to ‘passward’) the hash value will be totally different.
It is not easy to find the given input to a hash value. Hence, it is not easily feasible to find your password given the hash value.
Why use multiple iterations of the hash function?
To slow it down.
Basically, the way your find passwords is by trying all possibilities. You try ‘a’ and map it to check if that gives the password. Then you try ‘b’ and see.
If that process is slow, you decrease the odds of someone finding your password.
To demonstrate this we can use the cProfile library to investigate the difference in run-time. First let us try it with the 45454 iterations in the hash function.
import hashlib
import base64
import cProfile
def crack_password(entropy, iterations, salt):
alphabet = "abcdefghijklmnopqrtsuvwxyz"
for c1 in alphabet:
for c2 in alphabet:
password = str.encode(c1 + c2)
value = base64.b64encode(hashlib.pbkdf2_hmac('sha512', password, salt, iterations, 128))
if value == entropy:
return password
entropy = "kRqabDBsvkyAhpzzVWJtdqbtqgkgNPwr5gqWG6jvw73hxc7CCvC4E33WyR5bxKmAXG5vAG9/ue+DC7BYLHRfOTE/dLKSMdpE9RFH7ZlTp7GHdH5b5vaqQCcKlXAwkky786zvpucDIgGGTOyw6kKB5hqIXLX9chDvcPQksVrjmUs=".encode()
iterations = 45454
salt = base64.b64decode("6VuJKkHVTdDelbNMPBxzw7INW2NkYlR/LoW4OL7kVAI=".encode())
cProfile.run("crack_password(entropy, iterations, salt)")
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.
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.
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
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[c] = chars.pop(random.randint(0, len(chars) - 1))
return key
def print_key(key):
for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
print(c, key[c])
def encrypt(key, message):
cipher = ""
for c in message:
if c in key:
cipher += key[c]
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
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.
Caesar Cipher is a simple substitution cipher, which is limited to only shift the characters by fix number.
Imagine you got the message: BRX DUH DZHVRPH. What to make out of it. Makes no sense. Just ignore it, right?
Well, for the untrained eye, yes, ignore it. But why would anyone bother sending you that message? Aren’t you curious?
I would be. Let’s say your friend told you it was a Caesar Cipher with a 3-key shift. What does that mean?
In the picture below, you can see how the letters are shifted from the green letters (plain text), to the encrypted red letters (cipher text). As an example, A is encrypted to D, and B is encrypted to E, and so forth.
By using that strategy, you can also figure out that a cipher B (red letters) maps to the plain Y (green letters). A cipher R maps to the plain O. And so forth. That results in that your secret message decrypts to YOU ARE AWESOME.
What a nice message to get.
Implementation of Caesar Cipher in Python
Below is an example of how you could implement the Caesar Cipher in Python.
def generate_key(n):
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
key = {}
cnt = 0
for c in chars:
key[c] = chars[(cnt + n) % len(chars)]
cnt += 1
return key
def encrypt(key, message):
cipher = ""
for c in message:
if c in key:
cipher += key[c]
else:
cipher += c
return cipher
def get_decrypt_key(key):
dkey = {}
for k in key:
dkey[key[k]] = k
return dkey
key = generate_key(3)
dkey = generate_key(23)
cipher = encrypt(key, "YOU ARE AWESOME")
print(cipher)
message = encrypt(dkey, cipher)
print(message)
dkey = get_decrypt_key(key)
print(encrypt(dkey, cipher))
There are some things to notice. First of all, the generate_key function comes in handy, when we extend our cipher function to the more general substitution cipher.
Also, notice, that encryption and decryption are done with the same function, just different keys. The decryption key of key-3 is key-23.
See that you can actually calculate the decryption key quite nice, as done in the get_decrypt_key function, which can be used if this is extended to a general substitution cipher.
How to de-cipher a Caesar Cipher message
Image you had received the message BRX DUH DZHVRPH, but didn’t know the key. What to do?
No worries, there is a way to solve that problem efficiently.
The scenario is that Alice wants to send Bob a secret message, but someone (you) get’s a copy of the message (and you are called Eve).
The secrecy of the message was intended to be, that you (Eve) does not know the Algorithm (how the encryption is done). That might seems naive today, but back in the time of Caesar, this was the state of the art, and people were not that knowledgable about the art of cryptography.
But you are in luck. Yes, I am talking to you Eve.
You know the Algorithm and you are soon to figure out, that the key space is quite small. Actually it only contains 26 possible keys.
That is in the reach of you manually trying out all possible keys.
But you are in more luck, because you realised that you also have Python. So let’s try to fix it in Python.
def generate_key(n):
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
key = {}
cnt = 0
for c in chars:
key[c] = chars[(cnt + n) % len(chars)]
cnt += 1
return key
def encrypt(key, message):
cipher = ""
for c in message:
if c in key:
cipher += key[c]
else:
cipher += c
return cipher
cipher = "BRX DUH DZHVRPH"
for i in range(26):
key = generate_key(i)
message = encrypt(key, cipher)
print(message)
The security of Caesar Cipher was by keeping the Algorithm secret. That approach to security is not used anymore. Kerckhoffs’ Principle states that the adversary (Eve) should not be able to break the cipher even when she knows the Algorithm.