132 lines
4.0 KiB
Python
132 lines
4.0 KiB
Python
|
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))
|