Skip to content

Commit

Permalink
Added a description to one-time pad
Browse files Browse the repository at this point in the history
  • Loading branch information
nbro committed Mar 3, 2017
1 parent f7b031b commit 6e80472
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 57 deletions.
53 changes: 30 additions & 23 deletions ands/algorithms/crypto/caesar.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,18 @@
# References
- [https://learncryptography.com/classical-encryption/caesar-cipher](https://learncryptography.com/classical-encryption/caesar-cipher)
- [https://en.wikipedia.org/wiki/Ciphertext](https://en.wikipedia.org/wiki/Ciphertext)
- [https://en.wikipedia.org/wiki/Plaintext](https://en.wikipedia.org/wiki/Plaintext)
"""

__all__ = ["caesar_encrypt",
"caesar_decrypt",
"caesar_encrypt_with_multiple_keys",
"caesar_decrypt_with_multiple_keys"]

from random import choice

__all__ = ["encrypt",
"decrypt",
"encrypt_with_multiple_keys",
"decrypt_with_multiple_keys"]

# A number which conventionally represents the maximum number that the function `ord` can return,
# since it returns the Unicode code point for a one-character strings (assuming that 16-bit Unicode system).
MAX_MAPPED_INT = 2 ** 16 - 1
Expand All @@ -109,34 +111,39 @@ def _move_char(c: str, k: int) -> str:
return chr(ord(c) + k)


def caesar_encrypt(m: str, k: int) -> str:
"""Given a string `m` (i.e. the plaintext) and a non-negative key `k`,
it returns the encrypted version of `m` with the key `k` using the Caesar cipher algorithm,
def encrypt(plaintext: str, k: int) -> str:
"""Given a string `plaintext` and a non-negative key `k`,
it returns the encrypted version of `plaintext`
with the key `k` using the Caesar cipher algorithm,
over an alphabet of possible maximum value `MAX_MAPPED_INT`."""
return "".join(_move_char(c, k) for c in m)
return "".join(_move_char(c, k) for c in plaintext)


def caesar_decrypt(cipher: str, k: int) -> str:
"""Reverts the operation performed by `caesar_encrypt`."""
return "".join(_move_char(c, -k) for c in cipher)
def decrypt(ciphertext: str, k: int) -> str:
"""Reverts the operation performed by `encrypt`."""
return "".join(_move_char(c, -k) for c in ciphertext)


# Example of poly-alphabetic encryption

def caesar_encrypt_with_multiple_keys(m: str, keys: list) -> tuple:
"""Given a message m and a set of keys, it encrypts each symbol of m with a random key from `keys`.
def encrypt_with_multiple_keys(plaintext: str, keys: list) -> tuple:
"""Given a message `plaintext` and a set of `keys`,
it encrypts each symbol of plaintext with a random key from `keys`.
The random pattern of keys chosen is the second item of the tuple returned."""
pattern = []
cipher = []
keys_used = []
ciphertext = []

for c in m:
for c in plaintext:
k = choice(keys)
pattern.append(k)
cipher.append(_move_char(c, k))
keys_used.append(k)
ciphertext.append(_move_char(c, k))

return "".join(ciphertext), keys_used

return "".join(cipher), pattern

def decrypt_with_multiple_keys(ciphertext: str, keys_used: list) -> str:
"""Reverts the operation performed by `encrypt_with_multiple_keys`.
def caesar_decrypt_with_multiple_keys(cipher: str, pattern: list) -> tuple:
"""`len(pattern) == len(keys)`, where `keys` are the keys passed to `caesar_encrypt_with_multiple_keys`."""
return "".join(_move_char(cipher[i], -k) for i, k in enumerate(pattern))
Assumes `keys_used` is the list of keys used to encrypt a certain `plaintext`,
such that len(ciphertext) == len(plaintext)."""
return "".join(_move_char(ciphertext[i], -k) for i, k in enumerate(keys_used))
123 changes: 115 additions & 8 deletions ands/algorithms/crypto/one_time_pad.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,126 @@
# -*- coding: utf-8 -*-

"""
# Meta info
Author: Nelson Brochado
One Time Pad cipher algorithm, which provides 'perfect secrecy',
but has some drawbacks, for example the key used
must be at least of the same length of the original message.
Created: 10/08/2015
Updated: 03/03/2017
# Description
One time pad (or, in short, OTP) is an encryption technique that cannot be cracked,
but requires the use of a one-time pre-shared key the same size as the message being sent.
In this technique, a plaintext is paired with a random secret key (also referred to as a one-time pad).
Specifically, each bit or character of the plaintext is encrypted by combining it
with the corresponding bit or character from the one-time pad using _modular addition_.
If the key is truly random, is at least as long as the plaintext,
is never reused in whole or in part, and is kept completely secret,
then the resulting ciphertext will be impossible to decrypt or break.
It has also been proven that any cipher with the _perfect secrecy_ property
must use keys with effectively the same requirements as OTP keys.
However, practical problems (i.e. it's too expensive to exchange keys of the same length of the plaintext)
have prevented one-time pads from being widely used.
## Example
Suppose Alice wishes to send the message m = "hi" to Bob.
Assume the key was somehow previously produced and securely issued to both.
Lets assume key k = "ab".
Lets assume, for simplicity, that messages only contain lower case letters
from the English alphabet, so we have letters from 'a'..'z'.
Lets assume further that the mapping between these letters goes like follows
'a' -> 1,
'b' -> 2,
'c' -> 3,
...
'z' -> 26
Lets call this mapping function h. Lets call the reversing function f.
The general one-time pad algorithm proceeds as follows.
let c be an empty string (which will present the ciphertext at the end of the algorithm)
iterate through message m:
let n be the current iteration
let i me the nth character of m
let j me the nth character of k
let h(i) be the integer representation of i
let h(j) be the integer representation of j
x = XOR(h(i), h(j))
y = f(x)
c = c + y
Thus the one-time pad algorithm to encrypt the message m = "hi"
with the randomly generated key k = "ab" proceeds as follows.
c = ""
Let n = 1.
i = 'h'
j = 'a'
h(i) = 8
h(j) = 1
In binary, h(i) = 8 is 1000 and h(j) = 1 is 0001.
x = XOR(1000, 0001) = 1001, which is 9 in decimal.
y = f(1001) = 'i'
c = '' + 'i' = "i"
Let n = 2.
i = 'i'
j = 'b'
h(i) = 9
h(j) = 2
In binary, h(i) = 9 is 1001 and h(j) = 2 is 0010.
x = XOR(1001, 0010) = 1011, which is 11 in decimal.
y = f(1011) = 'k'
c = 'i' + 'k' = "ik"
So the encrypted text c = "ik".
To decrypt we simply apply the one-time pad with the same key but to c = "ik".
## Notes
- one-time pad provides "perfect" secrecy
- one-time pad requires a key of the same length of the plaintext
- one-time pad may be impractical for messages greater than a certain length
# References
- [https://learncryptography.com/classical-encryption/one-time-pad](https://learncryptography.com/classical-encryption/one-time-pad)
- [https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/one-time-pad](https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/one-time-pad)
- [http://python-reference.readthedocs.io/en/latest/docs/operators/bitwise_XOR.html](http://python-reference.readthedocs.io/en/latest/docs/operators/bitwise_XOR.html)
"""

__all__ = ["encrypt", "decrypt"]


def encrypt(message, key):
"""Encrypt message using key according to the one-time-pad algorithm."""
return "".join(chr(ord(i) ^ ord(j)) for (i, j) in zip(message, key))
def encrypt(plaintext: str, key: str) -> str:
"""Encrypts `plaintext` using `key` according to the one-time-pad algorithm."""
return "".join(chr(ord(p) ^ ord(k)) for (p, k) in zip(plaintext, key))


def decrypt(ciphertext, key):
"""Decript ciphertext using key according to the OTP algorithm."""
def decrypt(ciphertext: str, key: str) -> str:
"""Decrypts`ciphertext` using `key` according to the one-time-pad algorithm."""
return encrypt(ciphertext, key)
26 changes: 13 additions & 13 deletions tests/algorithms/crypto/test_caesar.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,36 @@
import unittest
from random import randint

from ands.algorithms.crypto.caesar import caesar_encrypt, caesar_decrypt, \
caesar_encrypt_with_multiple_keys, caesar_decrypt_with_multiple_keys, \
from ands.algorithms.crypto.caesar import encrypt, decrypt, \
encrypt_with_multiple_keys, decrypt_with_multiple_keys, \
MAX_MAPPED_INT
from tests.algorithms.crypto.util import *
from tests.algorithms.crypto.util import find_max_char_ord_value, generate_random_string, gen_rand_keys


class TestCaesarCipher(unittest.TestCase):
def template_test_one_key(self, n, size):
"""n is the number of iterations.
size is the size of the message."""
for _ in range(n):
m = gen_rand_message(size)
key = random.randint(1, MAX_MAPPED_INT - find_max(m))
cipher = caesar_encrypt(m, key)
o = caesar_decrypt(cipher, key)
m = generate_random_string(size)
key = randint(1, MAX_MAPPED_INT - find_max_char_ord_value(m))
cipher = encrypt(m, key)
o = decrypt(cipher, key)
self.assertEqual(m, o)

def template_test_multi_keys(self, n, size, total_keys):
for _ in range(n):
m = gen_rand_message(size)
keys = gen_rand_keys(total_keys, 1, MAX_MAPPED_INT - find_max(m))
cipher, pattern = caesar_encrypt_with_multiple_keys(m, keys)
o = caesar_decrypt_with_multiple_keys(cipher, pattern)
m = generate_random_string(size)
keys = gen_rand_keys(total_keys, 1, MAX_MAPPED_INT - find_max_char_ord_value(m))
cipher, pattern = encrypt_with_multiple_keys(m, keys)
o = decrypt_with_multiple_keys(cipher, pattern)
self.assertEqual(m, o)

def test_empty_message(self):
for i in range(100):
m = ""
cipher = caesar_encrypt(m, i)
o = caesar_decrypt(cipher, i)
cipher = encrypt(m, i)
o = decrypt(cipher, i)
self.assertEqual(m, o)

def test_encrypt_and_decrypt_size_1(self):
Expand Down
8 changes: 4 additions & 4 deletions tests/algorithms/crypto/test_one_time_pad.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
import unittest
from random import randint

from ands.algorithms.crypto.one_time_pad import *
from tests.algorithms.crypto.util import *
from ands.algorithms.crypto.one_time_pad import encrypt, decrypt
from tests.algorithms.crypto.util import generate_random_string


class TestOneTimePad(unittest.TestCase):
def template_test(self, n, m):
"""m is the size of the string and key.
n is the number of iterations."""
for _ in range(n):
message = gen_rand_message(m)
key = gen_key(m)
message = generate_random_string(m)
key = generate_random_string(m)
cipher_text = encrypt(message, key)
original = decrypt(cipher_text, key)
self.assertEqual(original, message)
Expand Down
12 changes: 3 additions & 9 deletions tests/algorithms/crypto/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,13 @@
import string


def gen_rand_message(size):
def generate_random_string(size):
return "".join(random.choice(string.printable) for _ in range(size))


def gen_key(size):
"""Generate a random key of printable characters."""
return gen_rand_message(size)


def gen_rand_keys(size, _min, _max):
return [random.randint(_min, _max) for _ in range(size)]


def find_max(m):
"""m is a message"""
return max(ord(c) for c in m)
def find_max_char_ord_value(message: str):
return max(ord(c) for c in message)

0 comments on commit 6e80472

Please sign in to comment.