import random from utils import AlphabetUtils as au from utils import CipherUtils as cu from chapter_three import LinearesSchieberegister as lsr def encrypt_text(cleartext: str, coefficients: [int], key: [int]) -> str: cleartext_bits = [] for char in cleartext: binary_index = au.get_binary_index_of_letter(char) binary_index_list = [] for bit in binary_index: binary_index_list.append(int(bit)) cleartext_bits.extend(binary_index_list) keystream = lsr.create_shift_register(key, coefficients) cipher_bits = cu.xor_two_lists(cleartext_bits, keystream) cipher_bits_string = ''.join([str(x) for x in cipher_bits]) return cipher_bits_string def decrypt_text(cipher_bits: str, coefficients: [int], key: [int]) -> str: cipher_bits_list = [int(cipher_bits[i:i + 1]) for i in range(0, len(cipher_bits), 1)] keystream = lsr.create_shift_register(key, coefficients) cleartext_bits = cu.xor_two_lists(cipher_bits_list, keystream) letter_split = [cleartext_bits[i:i + 6] for i in range(0, len(cleartext_bits), 6)] cleartext = '' for letter in letter_split: # Converts the list of integers to a single string letter_string = ''.join([str(x) for x in letter]) cleartext += au.get_letter_at_binary_index(letter_string) return cleartext def get_optimal_shift_register_coefficients(size: int) -> [int]: """ Returns a set of coefficients for a linear shift register with the given size that guarantees a maximum bit stream. The coefficients are taken from the following page: https://www.eng.auburn.edu/~strouce/class/elec6250/LFSRs.pdf :param size: The size of the linear shift register :return: The optimal coefficients """ match size: case 1: return [1] case 2: return [1, 1] case 3: return [0, 1, 1] case 4: return [0, 0, 1, 1] case 5: return [0, 0, 1, 0, 1] case 6: return [0, 0, 0, 0, 1, 1] case 7: return [0, 0, 0, 0, 0, 1, 1] case 8: return [0, 1, 1, 0, 0, 0, 1, 1] case 9: return [0, 0, 0, 0, 1, 0, 0, 0, 1] case 10: return [0, 0, 0, 0, 0, 0, 1, 0, 0, 1] case 11: return [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1] case 12: return [0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1] case 13: return [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1] case 14: return [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] case 15: return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] case _: return [] def generate_random_key(size: int) -> [int]: """ Generates a random key (initial filling of a linear shift register) of the desired length :param size: The length of the key (must equal the length of the wanted linear shift register) :return: The key """ return [random.randint(0, 1) for x in range(size)] if __name__ == '__main__': size = 15 coefficients = get_optimal_shift_register_coefficients(size) key = generate_random_key(size) print(key) encrypted = encrypt_text('BonkRocks', coefficients, key) print(encrypted) print(decrypt_text(encrypted, coefficients, key))