import math import random from utils import AlphabetUtils as au from utils import CipherUtils as cu def encrypt_text(cleartext: str, key: {}) -> str: """ Encrypts the given text with the given key :param cleartext: The text to encrypt :param key: The key to use. Has to be generated with the function below in order to work properly. :return: The encrypted text """ cleartext = cu.transform_invalid_chars(cleartext) letter_length = key['config']['letter_length'] resulting = '' for char in cleartext: possible_ciphers = key['data'][char.lower()] chosen_cipher = random.choice(possible_ciphers) resulting += str(chosen_cipher).zfill(letter_length) return resulting def decrypt_text(ciphertext: str, key: {}) -> str: """ Decrypts the given ciphertext with the given key :param ciphertext: The text to decrypt :param key: The key to use. Has to be generated with the function below in order to work properly. :return: The decrypted text """ letter_length = key['config']['letter_length'] letter_split = [ciphertext[i:i + letter_length] for i in range(0, len(ciphertext), letter_length)] transformed_key = _transform_key(key) resulting = '' for cipher_letter in letter_split: cleartext = transformed_key[int(cipher_letter)] resulting += str(cleartext) return resulting def _transform_key(key: {}) -> []: """ Transforms the given key into a format that can be easier used for decryption :param key: The key to transform :return: The transformed key. Contains a list where the index is the ciphertext and the value is the cleartext """ transformed_key = [None] * int(key['config']['max_cipher']) for letter in key['data'].keys(): possible_ciphers = key['data'][letter] for cipher in possible_ciphers: transformed_key[int(cipher)] = letter return transformed_key def generate_key(key_distribution: [int]) -> {}: """ Generates a key that can be used for this cipher by using the given key distribution profile :param key_distribution: The key distribution, e.g. number of possibilities per letter :return: The key as an object which contains the letter as key and the list of possible ciphers as value """ key = { 'config': {}, 'data': {} } # Generate the key possibilities amount_of_numbers = sum(key_distribution) key_possibilities = [i for i in range(amount_of_numbers)] random.shuffle(key_possibilities) # Because the ciphertext does not contain of letters but of numbers instead and these are dependent on the key, # we need to include the fixed length of each 'letter' here key['config']['letter_length'] = len(str(amount_of_numbers)) key['config']['max_cipher'] = str(amount_of_numbers) # Distribute the keys to the letters for letter_index in range(len(key_distribution)): letter = au.get_letter_at_index(letter_index) keys_for_this_letter = [] for key_possibility in range(key_distribution[letter_index]): keys_for_this_letter.append(key_possibilities.pop()) key['data'][letter] = keys_for_this_letter return key def generate_key_distribution(base: str, multiplier: int) -> [int]: """ Generates a key distribution profile based on the letter frequencies of the given input text. Larger inputs generally result in better key distribution profiles. :param base: The text to use as a sort of seed :param multiplier: A multiplier for the possibilities per letter :return: The key distribution profile as a list of integers """ letter_distribution = cu.calculate_frequency(base) return [math.ceil(i * 100 * multiplier) for i in letter_distribution] def get_german_key_distribution(multiplier: int) -> [int]: """ Returns the 'optimal' key distribution profile for german texts :param multiplier: A multiplier for the possibilities per letter :return: The key distribution profile as a list of integers """ return [math.ceil(i * 100 * multiplier) for i in cu.GERMAN_FREQUENCY_PROFILE] if __name__ == '__main__': dist = get_german_key_distribution(10) print(dist) key = generate_key(key_distribution=dist) print(key) ciphertext = encrypt_text('BonkRocks', key) print(ciphertext) print(decrypt_text(ciphertext, key))