|
class Cipher: |
|
""" |
|
https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher |
|
""" |
|
|
|
def __init__(self, key: str): |
|
self.key = key |
|
|
|
def encode(self, text: str, key: str = None) -> str: |
|
""" |
|
encode a simple string using the bellaso cypher aka vigenère cypher |
|
:param text: the text to encode |
|
:param key: (optional) the key to use for the encryption |
|
:returns: an ascii shifted string according the key |
|
""" |
|
key = self.__normalize_key(text, key) |
|
|
|
result = [] |
|
for i in range(0, len(text)): |
|
offset = self.__get_offset(i, key) |
|
result.append(ord(text[i]) - offset) |
|
|
|
return ''.join(f'{c:02x}' for c in result) |
|
|
|
def decode(self, text: str, key: str = None) -> str: |
|
""" |
|
decode an ascii string using the bellaso cypher aka vigenère cypher |
|
:param text: the text to encode |
|
:param key: (optional) the key to use for the encryption |
|
:returns: the decoded string from shifted ascii input |
|
""" |
|
key = self.__normalize_key(text, key) |
|
|
|
result = [] |
|
chunks = self.__split_word(text) |
|
for i in range(0, len(chunks)): |
|
offset = self.__get_offset(i, key) |
|
result.append(chr(int(chunks[i], base=16) + offset)) |
|
|
|
return ''.join(c for c in result) |
|
|
|
def __get_offset(self, index: int, key: str) -> int: |
|
""" |
|
get the offset for the given index in the key |
|
:param index: the pos as int |
|
:param key: the full key as str |
|
:returns: an int representing the offset |
|
""" |
|
if 0 == index: |
|
return self.__alpha_index(key[index]) - 1 if key[index] != ' ' else 0 |
|
return self.__alpha_index(key[index]) if key[index] != ' ' else 0 |
|
|
|
def __alpha_index(self, letter: str) -> int: |
|
""" |
|
return the index in the alpahbet for the given char |
|
:param letter: char for the lookup |
|
:returns: the position of the char in the alphabet |
|
""" |
|
alphabet = list(ascii_lowercase) |
|
return alphabet.index(letter.lower()) + 1 |
|
|
|
def __split_word(self, text: str, chunk_size: int = 2) -> []: |
|
""" |
|
Split text by chunk size (fixed length) |
|
:param text: the text to split |
|
:param chunk_size: (default=2) used to split |
|
:returns: list of 2 char length from the original string |
|
""" |
|
return [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)] |
|
|
|
def __normalize_key(self, text: str, key: str = None) -> str: |
|
""" |
|
normalize the key by length by making it as long as the text |
|
:param text: input text |
|
:param key: (optional) input key |
|
:returns: returns a key that is as long as the text |
|
""" |
|
if key is None: |
|
key = self.key |
|
|
|
if len(text) > len(key): |
|
mod = len(text) % len(key) |
|
div = (len(text) - mod) / len(key) |
|
|
|
return key * int(div) + key[:mod] |
|
|
|
return key[:len(text)] |