lab_encryption/ciphers/poly.py

70 lines
2.7 KiB
Python

from itertools import cycle
from easybits import Bits
class PolyCipher:
"""Implements a polyalphabetic cipher.
The polyalphabetic cipher is like a Caesar cipher except that
the secret number changes for each character to be encrypted or
decrypted. This makes frequency analysis much harder, because
each plaintext space can be encrypted as a different character.
int_min and int_max represent the lowest and highest allowed int values
of characters. They are set to include all the ASCII printable
characters (https://en.wikipedia.org/wiki/ASCII#Printable_character_table)
ASCII values outside this range (for example, '\n', the newline character),
just get passed through unencrypted.
"""
int_min = 32
int_max = 127
def __init__(self, secret):
self.secret = secret
def encrypt(self, plaintext):
"Converts a plaintext message into an encrypted ciphertext"
ciphertext = []
for char, secret_char in zip(plaintext, cycle(self.secret)):
plain_int = Bits(char, encoding='ascii').int
if self.int_min <= plain_int and plain_int < self.int_max:
secret_int = self.get_int(secret_char)
cipher_int = self.rotate(secret_int, plain_int)
ciphertext.append(Bits(cipher_int, length=8).ascii)
else:
ciphertext.append(char)
return ''.join(ciphertext)
def decrypt(self, ciphertext):
"Converts an encrypted ciphertext into a plaintext message"
plaintext = []
for char, secret_char in zip(ciphertext, cycle(self.secret)):
cipher_int = Bits(char, encoding='ascii').int
if self.int_min <= cipher_int and cipher_int < self.int_max:
secret_int = self.get_int(secret_char)
plain_int = self.rotate(-secret_int, cipher_int)
plaintext.append(Bits(plain_int, length=8).ascii)
else:
plaintext.append(char)
return ''.join(plaintext)
def rotate(self, secret, x):
"""Adds a secret number to x.
The modulo operator (%) is used to ensure that the result
is greater than equal to int_min and less than int_max.
"""
range_size = self.int_max - self.int_min
return (x + secret - self.int_min) % range_size + self.int_min
def get_int(self, secret_char):
"""Converts an int or a single-character string into an int.
When `secret_char` is an int, we just return it. Otherwise we
return the character's ASCII value.
"""
if isinstance(secret_char, int):
return secret_char
else:
return Bits(secret_char, encoding='ascii').int