From 087a01cd3d704fb54a35ada81a6fb6109b056397 Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Fri, 22 Oct 2021 17:35:22 +0200 Subject: [PATCH] #9: Implementing Stromchiffre - Also various bug fixes and improvements --- ...register.py => LinearesSchieberegister.py} | 12 +- chapter_three/Stromchiffre.py | 103 ++++++++++++++++++ utils/AlphabetUtils.py | 21 +++- 3 files changed, 128 insertions(+), 8 deletions(-) rename chapter_three/{Lineares-Schieberegister.py => LinearesSchieberegister.py} (77%) create mode 100644 chapter_three/Stromchiffre.py diff --git a/chapter_three/Lineares-Schieberegister.py b/chapter_three/LinearesSchieberegister.py similarity index 77% rename from chapter_three/Lineares-Schieberegister.py rename to chapter_three/LinearesSchieberegister.py index d212828..acee682 100644 --- a/chapter_three/Lineares-Schieberegister.py +++ b/chapter_three/LinearesSchieberegister.py @@ -16,7 +16,6 @@ def create_shift_register(init_state: [int], coefficients: [int]) -> [int]: """ return_list = [] current_state = init_state.copy() - current_state_list = [current_state.copy()] if (size := len(init_state)) != len(coefficients): print('Length of lists does not match.') @@ -26,15 +25,18 @@ def create_shift_register(init_state: [int], coefficients: [int]) -> [int]: return_list.append(current_state[-1]) new_state_value = 0 for i in range(size): - new_state_value = (new_state_value + current_state[i] ^ coefficients[i]) % 2 + new_state_value = (new_state_value + current_state[i] * coefficients[i]) % 2 current_state.insert(0, new_state_value) current_state.pop(-1) - if current_state in current_state_list: - return_list.extend(current_state[1:][::-1]) + if current_state == init_state: + # For ideal coefficients, the output is perfectly fine. + # However, for unideal coefficients, it might appear that we reach a state after lets say 1 iteration. + # We still need to output [size] amount of bits, hence the following line. + if x != (2 ** size) - 2: + return_list.extend(current_state[1:][::-1]) break - current_state_list.append(current_state.copy()) return return_list diff --git a/chapter_three/Stromchiffre.py b/chapter_three/Stromchiffre.py new file mode 100644 index 0000000..324c6ee --- /dev/null +++ b/chapter_three/Stromchiffre.py @@ -0,0 +1,103 @@ +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. + :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)) diff --git a/utils/AlphabetUtils.py b/utils/AlphabetUtils.py index 547b1ed..f6db06f 100644 --- a/utils/AlphabetUtils.py +++ b/utils/AlphabetUtils.py @@ -1,8 +1,10 @@ +import decimal + LETTERS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] -def is_letter_of_alphabet(letter: str): +def is_letter_of_alphabet(letter: str) -> bool: """ Checks if the given letter is a valid letter of the alphabet :param letter: The letter to check @@ -11,7 +13,7 @@ def is_letter_of_alphabet(letter: str): return letter.upper() in LETTERS -def get_letter_at_index(idx: int, capital: bool = False): +def get_letter_at_index(idx: int, capital: bool = False) -> str: """ Returns the letter at the given index :param idx: The index of the letter to return @@ -24,7 +26,7 @@ def get_letter_at_index(idx: int, capital: bool = False): return LETTERS[idx] if capital else LETTERS[idx].lower() -def get_index_of_letter(letter: str): +def get_index_of_letter(letter: str) -> int: """ Returns the index of the given letter :param letter: The letter to return the index of @@ -34,3 +36,16 @@ def get_index_of_letter(letter: str): raise AttributeError return LETTERS.index(letter.upper()) + +def get_binary_index_of_letter(letter: str) -> str: + """ + Returns the binary representation of the letter index + :param letter: The letter to return the index for + :return: The binary representation of the letter index + """ + char_index = get_index_of_letter(letter) + return bin(char_index)[2:].zfill(6) + +def get_letter_at_binary_index(index: str) -> str: + decimal_index = int(index, 2) + return get_letter_at_index(decimal_index) \ No newline at end of file