From 851fc3e9217c1e3e24a5b94a71b07afa5234feac Mon Sep 17 00:00:00 2001 From: nikos258 Date: 2025年7月11日 19:46:24 +0300 Subject: [PATCH 01/18] made find_key_from_vigenere_cipher --- ciphers/break_vigenere.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 ciphers/break_vigenere.py diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py new file mode 100644 index 000000000000..1abe8f5e9573 --- /dev/null +++ b/ciphers/break_vigenere.py @@ -0,0 +1,20 @@ +LETTER_FREQUENCIES_DICT = { + 'A': 8.12, 'B': 1.49, 'C': 2.71, 'D': 4.32, 'E': 12.02, 'F': 2.3, 'G': 2.03, + 'H': 5.92, 'I': 7.31, 'J': 0.1, 'K': 0.69, 'L': 3.92, 'M': 2.61, + 'N': 6.95, 'O': 7.68, 'P': 1.82, 'Q': 0.11, 'R': 6.02, 'S': 6.28, + 'T': 9.10, 'U': 2.88, 'V': 1.11, 'W': 2.09, 'X': 0.17, 'Y': 2.11, 'Z': 0.07 +} + +LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + +def find_key_from_vigenere_cipher(ciphertext: str) -> str: + clean_ciphertext = list() + for symbol in ciphertext: + if symbol in LETTERS: + clean_ciphertext.append(symbol.upper()) + + clean_ciphertext = "".join(clean_ciphertext) + + key = "" # todo replace with function + return key \ No newline at end of file From 6134fa10725be7443e3c17d67acab81033c845c5 Mon Sep 17 00:00:00 2001 From: nikos258 Date: 2025年7月11日 21:00:56 +0300 Subject: [PATCH 02/18] made index of coincidence --- ciphers/break_vigenere.py | 13 +++++++++++++ requirements.txt | 19 ------------------- 2 files changed, 13 insertions(+), 19 deletions(-) delete mode 100644 requirements.txt diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index 1abe8f5e9573..e231b0268f81 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -8,6 +8,19 @@ LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +def index_of_coincidence(frequencies: dict, length: int) -> float: + """ + Calculates the index of coincidence for a text. + :param frequencies: dictionary of the form {letter_of_the_alphabet: amount of times it appears in the text as a percentage} + :param length: the length of the text + :return the index of coincidence: + """ + index = 0.0 + for value in frequencies.values(): + index += (value/length)**2 + return index + + def find_key_from_vigenere_cipher(ciphertext: str) -> str: clean_ciphertext = list() for symbol in ciphertext: diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 66b5d8a6b94e..000000000000 --- a/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -beautifulsoup4 -fake-useragent -httpx -imageio -keras -lxml -matplotlib -numpy -opencv-python -pandas -pillow -rich -scikit-learn -sphinx-pyproject -statsmodels -sympy -tweepy -typing_extensions -xgboost From cc0416ca8e63cfbf16ee6df9324b6e38ed377c90 Mon Sep 17 00:00:00 2001 From: nikos258 Date: 2025年7月11日 21:18:15 +0300 Subject: [PATCH 03/18] made calculate_indexes_of_coincidence --- ciphers/break_vigenere.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index e231b0268f81..cadb1284aafa 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -21,6 +21,33 @@ def index_of_coincidence(frequencies: dict, length: int) -> float: return index +def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: + """ + For each number j in the range [0, step) the function checks the letters of the ciphertext whose position has the + form j+n*step, where n is an integer and for these letters it calculates the index of coincidence. It returns a list + with step elements, which represent the indexes of coincidence. + :param ciphertext: s string (text) + :param step: the step when traversing through the cipher + :return: a list with the indexes of coincidence + """ + indexes_of_coincidence = list() + length = len(ciphertext) + + # for every starting point in [0, step) + for j in range(step): + frequencies = dict() + c = 0 + for i in range(0+j, length, step): + c += 1 + try: # in case the frequencies dictionary does not already have this key + frequencies[ciphertext[i]] += 1 + except KeyError: + frequencies[ciphertext[i]] = 1 + indexes_of_coincidence.append(index_of_coincidence(frequencies, c)) + + return indexes_of_coincidence + + def find_key_from_vigenere_cipher(ciphertext: str) -> str: clean_ciphertext = list() for symbol in ciphertext: @@ -30,4 +57,8 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: clean_ciphertext = "".join(clean_ciphertext) key = "" # todo replace with function - return key \ No newline at end of file + return key + + +if __name__ == '__main__': + print(index_of_coincidence(LETTER_FREQUENCIES_DICT, 1000)) \ No newline at end of file From e120ae40ae2cd2261b88e664141b54015e5a5db1 Mon Sep 17 00:00:00 2001 From: nikos258 Date: 2025年7月12日 23:20:46 +0300 Subject: [PATCH 04/18] made friedman_method --- ciphers/break_vigenere.py | 42 +++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index cadb1284aafa..226cb7239b33 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -4,8 +4,8 @@ 'N': 6.95, 'O': 7.68, 'P': 1.82, 'Q': 0.11, 'R': 6.02, 'S': 6.28, 'T': 9.10, 'U': 2.88, 'V': 1.11, 'W': 2.09, 'X': 0.17, 'Y': 2.11, 'Z': 0.07 } - LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +PARAMETER = 0.0665 # index of confidence of the entire language (for english 0.0665) def index_of_coincidence(frequencies: dict, length: int) -> float: @@ -17,7 +17,7 @@ def index_of_coincidence(frequencies: dict, length: int) -> float: """ index = 0.0 for value in frequencies.values(): - index += (value/length)**2 + index += (value / length) ** 2 return index @@ -37,7 +37,7 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: for j in range(step): frequencies = dict() c = 0 - for i in range(0+j, length, step): + for i in range(0 + j, length, step): c += 1 try: # in case the frequencies dictionary does not already have this key frequencies[ciphertext[i]] += 1 @@ -48,6 +48,40 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: return indexes_of_coincidence +def friedman_method(ciphertext: str, max_keylength: int=None) -> int: + """ + Implements Friedman's method for finding the length of the key of a Vigenere cipher. It finds the length with an + index of confidence closer to that of an average text in the english language. + :param ciphertext: a string (text) + :param max_keylength: the maximum length of key that Friedman's method should check, if None then it defaults to the + length of the cipher + :return: the length of the key + """ + # sets the default value of MAX_KEYLEBGTH + if max_keylength is None: + max_keylength = len(ciphertext) + + frequencies = [1.5] # the zeroth position should not be used: length of key is greater than zero + + # for every length of key + for i in range(1, max_keylength + 1): + + # for a specific length it finds the minimum index of coincidence + min1 = 15.0 + for val in calculate_indexes_of_coincidence(ciphertext, i): + if abs(val - PARAMETER) < abs(min1 - PARAMETER): + min1 = val + frequencies.append(min1) + + # finds which length of key has the minimum difference with the language PARAMETER + li = (15.0, -1) # initialization + for i in range(len(frequencies)): + if abs(frequencies[i] - PARAMETER) < abs(li[0] - PARAMETER): + li = (frequencies[i], i) + + return li[1] + + def find_key_from_vigenere_cipher(ciphertext: str) -> str: clean_ciphertext = list() for symbol in ciphertext: @@ -61,4 +95,4 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: if __name__ == '__main__': - print(index_of_coincidence(LETTER_FREQUENCIES_DICT, 1000)) \ No newline at end of file + print(index_of_coincidence(LETTER_FREQUENCIES_DICT, 1000)) From 59d3740a795b52cacdf5afcb59979e9ddf4516f1 Mon Sep 17 00:00:00 2001 From: nikos258 Date: 2025年7月13日 00:22:00 +0300 Subject: [PATCH 05/18] made find_key and get_frequencies --- ciphers/break_vigenere.py | 69 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index 226cb7239b33..258048e609b4 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -6,6 +6,7 @@ } LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" PARAMETER = 0.0665 # index of confidence of the entire language (for english 0.0665) +MAX_KEYLENGTH = 10 # None is the default def index_of_coincidence(frequencies: dict, length: int) -> float: @@ -82,17 +83,77 @@ def friedman_method(ciphertext: str, max_keylength: int=None) -> int: return li[1] +def get_frequencies() -> tuple: + """Return the values of the global variable @letter_frequencies_dict as a tuple ex. (0.25, 1.42, ...).""" + t = tuple(LETTER_FREQUENCIES_DICT[chr(i)] for i in range(ord('A'), ord('A') + 26)) + return tuple(num / 100 for num in t) + + +def find_key(ciphertext: str, key_length: int) -> str: + """ + Finds the key of a text which has been encrypted with the Vigenere algorithm, using statistical analysis. + The function needs an estimation of the length of the key. Firstly it finds the frequencies of the letters in the + text. Then it compares these frequencies with those of an average text in the english language. For each letter it + multiplies its frequency with the average one and adds them all together, then it shifts the frequencies of the text + cyclically by one position and repeats the process. The shift that produces the largest sum corresponds to a letter + of the key. The whole procedure takes place for every letter of the key (essentially as many times as the length + of the key). + :param ciphertext: a string (text) + :param key_length: a supposed length of the key + :return: the key as a string + """ + a = ord('A') + cipher_length = len(ciphertext) + alphabet_length = 26 # the length of the english alphabet + + key = [] + + # for every letter of the key + for k in range(key_length): + # find the frequencies of the letters in the message: + # the frequency of 'A' is in the first position of the freq list and so on + freq = [0]*alphabet_length + c = 0 + for i in range(k, cipher_length, key_length): + freq[ord(ciphertext[i]) - a] += 1 + c += 1 + freq = [num / c for num in freq] + + # find the max sum -> part of the key + real_freq = get_frequencies() + max1 = [-1, None] # value, position + for i in range(alphabet_length): + new_val = sum((freq[j] * real_freq[j]) for j in range(alphabet_length)) + if max1[0] < new_val: + max1 = [new_val, i] + freq.append(freq.pop(0)) # shift the list cyclically one position to the left + key.append(max1[1]) + + return "".join(chr(num + a) for num in key) # return the key as a string + + def find_key_from_vigenere_cipher(ciphertext: str) -> str: clean_ciphertext = list() - for symbol in ciphertext: + for symbol in ciphertext.upper(): if symbol in LETTERS: - clean_ciphertext.append(symbol.upper()) + clean_ciphertext.append(symbol) clean_ciphertext = "".join(clean_ciphertext) + print(clean_ciphertext) + + key_length = friedman_method(clean_ciphertext, max_keylength=MAX_KEYLENGTH) + print(f"The length of the key is {key_length}") + if key_length <= 0: + print("Something went wrong while calculating the length of the key.") + return "" - key = "" # todo replace with function + key = find_key(clean_ciphertext, key_length) return key if __name__ == '__main__': - print(index_of_coincidence(LETTER_FREQUENCIES_DICT, 1000)) + # print(index_of_coincidence(LETTER_FREQUENCIES_DICT, 1000)) + with open("out.txt") as file: + c = file.read() + k = find_key_from_vigenere_cipher(c) + print(k) \ No newline at end of file From d8c6d02c2c4ee9a50e1e97044914cbbeeb26fec7 Mon Sep 17 00:00:00 2001 From: nikos258 Date: 2025年7月13日 17:22:42 +0300 Subject: [PATCH 06/18] made more comments --- ciphers/break_vigenere.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index 258048e609b4..de53bdffbc41 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -6,7 +6,7 @@ } LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" PARAMETER = 0.0665 # index of confidence of the entire language (for english 0.0665) -MAX_KEYLENGTH = 10 # None is the default +MAX_KEYLENGTH = None # None is the default, you can also try a positive integer (example: 10) def index_of_coincidence(frequencies: dict, length: int) -> float: @@ -14,12 +14,14 @@ def index_of_coincidence(frequencies: dict, length: int) -> float: Calculates the index of coincidence for a text. :param frequencies: dictionary of the form {letter_of_the_alphabet: amount of times it appears in the text as a percentage} :param length: the length of the text - :return the index of coincidence: + :return: the index of coincidence +>>> index_of_coincidence({'A':1,'D':2,'E':3,'F':1,'H':1,'L': 2,'N':1,'T':1,'W':1}, 13) + 0.0641025641025641 """ index = 0.0 for value in frequencies.values(): - index += (value / length) ** 2 - return index + index += value * (value-1) + return index / (length * (length-1)) def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: @@ -44,7 +46,8 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: frequencies[ciphertext[i]] += 1 except KeyError: frequencies[ciphertext[i]] = 1 - indexes_of_coincidence.append(index_of_coincidence(frequencies, c)) + if c> 1: # to avoid division by zero in the index_of_coincidence function + indexes_of_coincidence.append(index_of_coincidence(frequencies, c)) return indexes_of_coincidence @@ -52,7 +55,9 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: def friedman_method(ciphertext: str, max_keylength: int=None) -> int: """ Implements Friedman's method for finding the length of the key of a Vigenere cipher. It finds the length with an - index of confidence closer to that of an average text in the english language. + index of confidence closer to that of an average text in the english language. Check the wikipedia page: + https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher + The algorithm is in the book "Introduction to Cryptography", K. Draziotis https://repository.kallipos.gr/handle/11419/8183 :param ciphertext: a string (text) :param max_keylength: the maximum length of key that Friedman's method should check, if None then it defaults to the length of the cipher @@ -84,7 +89,7 @@ def friedman_method(ciphertext: str, max_keylength: int=None) -> int: def get_frequencies() -> tuple: - """Return the values of the global variable @letter_frequencies_dict as a tuple ex. (0.25, 1.42, ...).""" + """Return the values of the global variable @LETTER_FREQUENCIES_DICT as a tuple ex. (0.25, 1.42, ...).""" t = tuple(LETTER_FREQUENCIES_DICT[chr(i)] for i in range(ord('A'), ord('A') + 26)) return tuple(num / 100 for num in t) @@ -97,7 +102,7 @@ def find_key(ciphertext: str, key_length: int) -> str: multiplies its frequency with the average one and adds them all together, then it shifts the frequencies of the text cyclically by one position and repeats the process. The shift that produces the largest sum corresponds to a letter of the key. The whole procedure takes place for every letter of the key (essentially as many times as the length - of the key). + of the key). See here: https://www.youtube.com/watch?v=LaWp_Kq0cKs :param ciphertext: a string (text) :param key_length: a supposed length of the key :return: the key as a string @@ -133,27 +138,27 @@ def find_key(ciphertext: str, key_length: int) -> str: def find_key_from_vigenere_cipher(ciphertext: str) -> str: + """ + Tries to find the key length and then the actual key of a Vigenere ciphertext. It uses Friedman's method and + statistical analysis. It works best for large pieces of text written in the english language. + """ clean_ciphertext = list() for symbol in ciphertext.upper(): if symbol in LETTERS: clean_ciphertext.append(symbol) clean_ciphertext = "".join(clean_ciphertext) - print(clean_ciphertext) key_length = friedman_method(clean_ciphertext, max_keylength=MAX_KEYLENGTH) print(f"The length of the key is {key_length}") if key_length <= 0: - print("Something went wrong while calculating the length of the key.") - return "" + raise ValueError("The length of the key should be strictly positive") key = find_key(clean_ciphertext, key_length) return key if __name__ == '__main__': - # print(index_of_coincidence(LETTER_FREQUENCIES_DICT, 1000)) - with open("out.txt") as file: - c = file.read() + c = "" k = find_key_from_vigenere_cipher(c) - print(k) \ No newline at end of file + print(k) From 8c48e8e57235143ead563eaaeaa2c6051ac38f29 Mon Sep 17 00:00:00 2001 From: nikos258 Date: 2025年7月13日 18:35:54 +0300 Subject: [PATCH 07/18] pre-commit 1 --- ciphers/break_vigenere.py | 65 +++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index de53bdffbc41..c934e23dc1d9 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -1,12 +1,36 @@ LETTER_FREQUENCIES_DICT = { - 'A': 8.12, 'B': 1.49, 'C': 2.71, 'D': 4.32, 'E': 12.02, 'F': 2.3, 'G': 2.03, - 'H': 5.92, 'I': 7.31, 'J': 0.1, 'K': 0.69, 'L': 3.92, 'M': 2.61, - 'N': 6.95, 'O': 7.68, 'P': 1.82, 'Q': 0.11, 'R': 6.02, 'S': 6.28, - 'T': 9.10, 'U': 2.88, 'V': 1.11, 'W': 2.09, 'X': 0.17, 'Y': 2.11, 'Z': 0.07 + "A": 8.12, + "B": 1.49, + "C": 2.71, + "D": 4.32, + "E": 12.02, + "F": 2.3, + "G": 2.03, + "H": 5.92, + "I": 7.31, + "J": 0.1, + "K": 0.69, + "L": 3.92, + "M": 2.61, + "N": 6.95, + "O": 7.68, + "P": 1.82, + "Q": 0.11, + "R": 6.02, + "S": 6.28, + "T": 9.10, + "U": 2.88, + "V": 1.11, + "W": 2.09, + "X": 0.17, + "Y": 2.11, + "Z": 0.07, } LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" PARAMETER = 0.0665 # index of confidence of the entire language (for english 0.0665) -MAX_KEYLENGTH = None # None is the default, you can also try a positive integer (example: 10) +MAX_KEYLENGTH = ( + None # None is the default, you can also try a positive integer (example: 10) +) def index_of_coincidence(frequencies: dict, length: int) -> float: @@ -20,8 +44,8 @@ def index_of_coincidence(frequencies: dict, length: int) -> float: """ index = 0.0 for value in frequencies.values(): - index += value * (value-1) - return index / (length * (length-1)) + index += value * (value - 1) + return index / (length * (length - 1)) def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: @@ -38,7 +62,7 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: # for every starting point in [0, step) for j in range(step): - frequencies = dict() + frequencies = dict[str, int] c = 0 for i in range(0 + j, length, step): c += 1 @@ -52,7 +76,7 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: return indexes_of_coincidence -def friedman_method(ciphertext: str, max_keylength: int=None) -> int: +def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int: """ Implements Friedman's method for finding the length of the key of a Vigenere cipher. It finds the length with an index of confidence closer to that of an average text in the english language. Check the wikipedia page: @@ -67,11 +91,12 @@ def friedman_method(ciphertext: str, max_keylength: int=None) -> int: if max_keylength is None: max_keylength = len(ciphertext) - frequencies = [1.5] # the zeroth position should not be used: length of key is greater than zero + frequencies = [ + 1.5 + ] # the zeroth position should not be used: length of key is greater than zero # for every length of key for i in range(1, max_keylength + 1): - # for a specific length it finds the minimum index of coincidence min1 = 15.0 for val in calculate_indexes_of_coincidence(ciphertext, i): @@ -90,7 +115,7 @@ def friedman_method(ciphertext: str, max_keylength: int=None) -> int: def get_frequencies() -> tuple: """Return the values of the global variable @LETTER_FREQUENCIES_DICT as a tuple ex. (0.25, 1.42, ...).""" - t = tuple(LETTER_FREQUENCIES_DICT[chr(i)] for i in range(ord('A'), ord('A') + 26)) + t = tuple(LETTER_FREQUENCIES_DICT[chr(i)] for i in range(ord("A"), ord("A") + 26)) return tuple(num / 100 for num in t) @@ -107,7 +132,7 @@ def find_key(ciphertext: str, key_length: int) -> str: :param key_length: a supposed length of the key :return: the key as a string """ - a = ord('A') + a = ord("A") cipher_length = len(ciphertext) alphabet_length = 26 # the length of the english alphabet @@ -117,7 +142,7 @@ def find_key(ciphertext: str, key_length: int) -> str: for k in range(key_length): # find the frequencies of the letters in the message: # the frequency of 'A' is in the first position of the freq list and so on - freq = [0]*alphabet_length + freq = [0.0] * alphabet_length c = 0 for i in range(k, cipher_length, key_length): freq[ord(ciphertext[i]) - a] += 1 @@ -131,7 +156,9 @@ def find_key(ciphertext: str, key_length: int) -> str: new_val = sum((freq[j] * real_freq[j]) for j in range(alphabet_length)) if max1[0] < new_val: max1 = [new_val, i] - freq.append(freq.pop(0)) # shift the list cyclically one position to the left + freq.append( + freq.pop(0) + ) # shift the list cyclically one position to the left key.append(max1[1]) return "".join(chr(num + a) for num in key) # return the key as a string @@ -142,12 +169,12 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: Tries to find the key length and then the actual key of a Vigenere ciphertext. It uses Friedman's method and statistical analysis. It works best for large pieces of text written in the english language. """ - clean_ciphertext = list() + clean_ciphertext_list = list() for symbol in ciphertext.upper(): if symbol in LETTERS: - clean_ciphertext.append(symbol) + clean_ciphertext_list.append(symbol) - clean_ciphertext = "".join(clean_ciphertext) + clean_ciphertext = "".join(clean_ciphertext_list) key_length = friedman_method(clean_ciphertext, max_keylength=MAX_KEYLENGTH) print(f"The length of the key is {key_length}") @@ -158,7 +185,7 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: return key -if __name__ == '__main__': +if __name__ == "__main__": c = "" k = find_key_from_vigenere_cipher(c) print(k) From 50da9d3baa4446c5b479f2652a680183e5c97003 Mon Sep 17 00:00:00 2001 From: nikos258 Date: 2025年7月13日 18:49:51 +0300 Subject: [PATCH 08/18] pre-commit 2 --- ciphers/break_vigenere.py | 78 +++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index c934e23dc1d9..4a0ae9a1ba5d 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -27,20 +27,22 @@ "Z": 0.07, } LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -PARAMETER = 0.0665 # index of confidence of the entire language (for english 0.0665) +PARAMETER = 0.0665 # index of confidence of the entire language (for +# english 0.0665) MAX_KEYLENGTH = ( - None # None is the default, you can also try a positive integer (example: 10) + None # None is the default, you can also try a positive integer ( + # example: 10) ) def index_of_coincidence(frequencies: dict, length: int) -> float: """ Calculates the index of coincidence for a text. - :param frequencies: dictionary of the form {letter_of_the_alphabet: amount of times it appears in the text as a percentage} + :param frequencies: dictionary of the form {letter_of_the_alphabet: amount + of times it appears in the text as a percentage} :param length: the length of the text :return: the index of coincidence ->>> index_of_coincidence({'A':1,'D':2,'E':3,'F':1,'H':1,'L': 2,'N':1,'T':1,'W':1}, 13) - 0.0641025641025641 +>>> index_of_coincidence({'A':1,'D':2,'E':3,'F':1,'H':1,'L': 2,'N':1,'T':1,'W':1}, 13) 0.0641025641025641 """ index = 0.0 for value in frequencies.values(): @@ -50,9 +52,10 @@ def index_of_coincidence(frequencies: dict, length: int) -> float: def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: """ - For each number j in the range [0, step) the function checks the letters of the ciphertext whose position has the - form j+n*step, where n is an integer and for these letters it calculates the index of coincidence. It returns a list - with step elements, which represent the indexes of coincidence. + For each number j in the range [0, step) the function checks the letters of + the ciphertext whose position has the form j+n*step, where n is an integer + and for these letters it calculates the index of coincidence. It returns a + list with step elements, which represent the indexes of coincidence. :param ciphertext: s string (text) :param step: the step when traversing through the cipher :return: a list with the indexes of coincidence @@ -66,11 +69,13 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: c = 0 for i in range(0 + j, length, step): c += 1 - try: # in case the frequencies dictionary does not already have this key + try: # in case the frequencies dictionary does not already have + # this key frequencies[ciphertext[i]] += 1 except KeyError: frequencies[ciphertext[i]] = 1 - if c> 1: # to avoid division by zero in the index_of_coincidence function + if c> 1: # to avoid division by zero in the index_of_coincidence + # function indexes_of_coincidence.append(index_of_coincidence(frequencies, c)) return indexes_of_coincidence @@ -78,22 +83,25 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int: """ - Implements Friedman's method for finding the length of the key of a Vigenere cipher. It finds the length with an - index of confidence closer to that of an average text in the english language. Check the wikipedia page: - https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher - The algorithm is in the book "Introduction to Cryptography", K. Draziotis https://repository.kallipos.gr/handle/11419/8183 + Implements Friedman's method for finding the length of the key of a + Vigenere cipher. It finds the length with an index of confidence closer + to that of an average text in the english language. Check the wikipedia + page: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher The algorithm + is in the book "Introduction to Cryptography", K. Draziotis + https://repository.kallipos.gr/handle/11419/8183 :param ciphertext: a string (text) - :param max_keylength: the maximum length of key that Friedman's method should check, if None then it defaults to the - length of the cipher + :param max_keylength: the maximum length of key that Friedman's method + should check, if None then it defaults to the length of the cipher :return: the length of the key """ - # sets the default value of MAX_KEYLEBGTH + # sets the default value of MAX_KEYLENGTH if max_keylength is None: max_keylength = len(ciphertext) frequencies = [ 1.5 - ] # the zeroth position should not be used: length of key is greater than zero + ] # the zeroth position should not be used: length of key is greater + # than zero # for every length of key for i in range(1, max_keylength + 1): @@ -104,7 +112,8 @@ def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int: min1 = val frequencies.append(min1) - # finds which length of key has the minimum difference with the language PARAMETER + # finds which length of key has the minimum difference with the language + # PARAMETER li = (15.0, -1) # initialization for i in range(len(frequencies)): if abs(frequencies[i] - PARAMETER) < abs(li[0] - PARAMETER): @@ -114,20 +123,26 @@ def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int: def get_frequencies() -> tuple: - """Return the values of the global variable @LETTER_FREQUENCIES_DICT as a tuple ex. (0.25, 1.42, ...).""" + """Return the values of the global variable @LETTER_FREQUENCIES_DICT as a + tuple ex. (0.25, 1.42, ...). + """ t = tuple(LETTER_FREQUENCIES_DICT[chr(i)] for i in range(ord("A"), ord("A") + 26)) return tuple(num / 100 for num in t) def find_key(ciphertext: str, key_length: int) -> str: """ - Finds the key of a text which has been encrypted with the Vigenere algorithm, using statistical analysis. - The function needs an estimation of the length of the key. Firstly it finds the frequencies of the letters in the - text. Then it compares these frequencies with those of an average text in the english language. For each letter it - multiplies its frequency with the average one and adds them all together, then it shifts the frequencies of the text - cyclically by one position and repeats the process. The shift that produces the largest sum corresponds to a letter - of the key. The whole procedure takes place for every letter of the key (essentially as many times as the length - of the key). See here: https://www.youtube.com/watch?v=LaWp_Kq0cKs + Finds the key of a text which has been encrypted with the Vigenere + algorithm, using statistical analysis. The function needs an estimation + of the length of the key. Firstly it finds the frequencies of the + letters in the text. Then it compares these frequencies with those of an + average text in the english language. For each letter it multiplies its + frequency with the average one and adds them all together, then it + shifts the frequencies of the text cyclically by one position and + repeats the process. The shift that produces the largest sum corresponds + to a letter of the key. The whole procedure takes place for every letter + of the key (essentially as many times as the length of the key). See + here: https://www.youtube.com/watch?v=LaWp_Kq0cKs :param ciphertext: a string (text) :param key_length: a supposed length of the key :return: the key as a string @@ -140,8 +155,8 @@ def find_key(ciphertext: str, key_length: int) -> str: # for every letter of the key for k in range(key_length): - # find the frequencies of the letters in the message: - # the frequency of 'A' is in the first position of the freq list and so on + # find the frequencies of the letters in the message: the frequency + # of 'A' is in the first position of the freq list and so on freq = [0.0] * alphabet_length c = 0 for i in range(k, cipher_length, key_length): @@ -166,8 +181,9 @@ def find_key(ciphertext: str, key_length: int) -> str: def find_key_from_vigenere_cipher(ciphertext: str) -> str: """ - Tries to find the key length and then the actual key of a Vigenere ciphertext. It uses Friedman's method and - statistical analysis. It works best for large pieces of text written in the english language. + Tries to find the key length and then the actual key of a Vigenere + ciphertext. It uses Friedman's method and statistical analysis. It works + best for large pieces of text written in the english language. """ clean_ciphertext_list = list() for symbol in ciphertext.upper(): From c9b22da28ba78af488729f3a1d8a0ddf4c61a3bb Mon Sep 17 00:00:00 2001 From: nikos258 Date: Sun, 7 Sep 2025 16:17:14 +0300 Subject: [PATCH 09/18] made friedman_method --- ciphers/break_vigenere.py | 20 +++++++++++++------- ciphers/vigenere_cipher.py | 4 ++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index 4a0ae9a1ba5d..afb2d9c3e462 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -42,7 +42,6 @@ def index_of_coincidence(frequencies: dict, length: int) -> float: of times it appears in the text as a percentage} :param length: the length of the text :return: the index of coincidence ->>> index_of_coincidence({'A':1,'D':2,'E':3,'F':1,'H':1,'L': 2,'N':1,'T':1,'W':1}, 13) 0.0641025641025641 """ index = 0.0 for value in frequencies.values(): @@ -60,18 +59,20 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list: :param step: the step when traversing through the cipher :return: a list with the indexes of coincidence """ - indexes_of_coincidence = list() + indexes_of_coincidence = [] length = len(ciphertext) # for every starting point in [0, step) for j in range(step): - frequencies = dict[str, int] + frequencies: dict[str, int] = {} c = 0 for i in range(0 + j, length, step): c += 1 try: # in case the frequencies dictionary does not already have # this key - frequencies[ciphertext[i]] += 1 + letter = ciphertext[i] + temp = frequencies[letter] + frequencies[ciphertext[i]] = temp + 1 except KeyError: frequencies[ciphertext[i]] = 1 if c> 1: # to avoid division by zero in the index_of_coincidence @@ -94,7 +95,7 @@ def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int: should check, if None then it defaults to the length of the cipher :return: the length of the key """ - # sets the default value of MAX_KEYLENGTH + # sets the default value of max_keylength if max_keylength is None: max_keylength = len(ciphertext) @@ -176,7 +177,12 @@ def find_key(ciphertext: str, key_length: int) -> str: ) # shift the list cyclically one position to the left key.append(max1[1]) - return "".join(chr(num + a) for num in key) # return the key as a string + key_as_list_of_letters = [] + for num in key: + if num is not None: + key_as_list_of_letters.append(chr(num + a)) + + return "".join(key_as_list_of_letters) # return the key as a string def find_key_from_vigenere_cipher(ciphertext: str) -> str: @@ -185,7 +191,7 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: ciphertext. It uses Friedman's method and statistical analysis. It works best for large pieces of text written in the english language. """ - clean_ciphertext_list = list() + clean_ciphertext_list = [] for symbol in ciphertext.upper(): if symbol in LETTERS: clean_ciphertext_list.append(symbol) diff --git a/ciphers/vigenere_cipher.py b/ciphers/vigenere_cipher.py index e76161351fb1..2cfab9dc9e90 100644 --- a/ciphers/vigenere_cipher.py +++ b/ciphers/vigenere_cipher.py @@ -2,6 +2,8 @@ def main() -> None: + # with open("file.txt", "r") as file: + # message = file.read() message = input("Enter message: ") key = input("Enter key [alphanumeric]: ") mode = input("Encrypt/Decrypt [e/d]: ") @@ -15,6 +17,8 @@ def main() -> None: print(f"\n{mode.title()}ed message:") print(translated) + # with open("out.txt", "w") as file: + # file.write(translated) def encrypt_message(key: str, message: str) -> str: From d75ab4d654b3af3aa42348a1270c87bc8b6aa88a Mon Sep 17 00:00:00 2001 From: nikos258 Date: Mon, 8 Sep 2025 16:21:01 +0300 Subject: [PATCH 10/18] updated the main function and undid accidental changes in vigenere_cipher.py --- ciphers/break_vigenere.py | 7 ++++--- ciphers/vigenere_cipher.py | 4 ---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index afb2d9c3e462..e00a61fa13df 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -208,6 +208,7 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: if __name__ == "__main__": - c = "" - k = find_key_from_vigenere_cipher(c) - print(k) + with open("ciphertext.txt", "r") as file: # insert the name of your file + ciphertext = file.read() + key = find_key_from_vigenere_cipher(ciphertext) + print(key) diff --git a/ciphers/vigenere_cipher.py b/ciphers/vigenere_cipher.py index 2cfab9dc9e90..e76161351fb1 100644 --- a/ciphers/vigenere_cipher.py +++ b/ciphers/vigenere_cipher.py @@ -2,8 +2,6 @@ def main() -> None: - # with open("file.txt", "r") as file: - # message = file.read() message = input("Enter message: ") key = input("Enter key [alphanumeric]: ") mode = input("Encrypt/Decrypt [e/d]: ") @@ -17,8 +15,6 @@ def main() -> None: print(f"\n{mode.title()}ed message:") print(translated) - # with open("out.txt", "w") as file: - # file.write(translated) def encrypt_message(key: str, message: str) -> str: From 7cdbd5e4027b559a0501881c6eb7780e27a8f8e3 Mon Sep 17 00:00:00 2001 From: nikos258 Date: Mon, 8 Sep 2025 17:03:36 +0300 Subject: [PATCH 11/18] fixed some errors to pass the tests --- ciphers/break_vigenere.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index e00a61fa13df..f73f921a4009 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -208,7 +208,9 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: if __name__ == "__main__": - with open("ciphertext.txt", "r") as file: # insert the name of your file - ciphertext = file.read() - key = find_key_from_vigenere_cipher(ciphertext) - print(key) + pass + # # how to execute + # with open("ciphertext.txt") as file: + # ciphertext = file.read() + # key = find_key_from_vigenere_cipher(ciphertext) + # print(key) From becfd071a0f5ff6e7b77c996da375e2d0779d118 Mon Sep 17 00:00:00 2001 From: nikos258 Date: Mon, 8 Sep 2025 17:44:52 +0300 Subject: [PATCH 12/18] made test for index_of_coincidence --- ciphers/break_vigenere.py | 10 ++++++++-- ciphers/tests/test.py | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 ciphers/tests/test.py diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index f73f921a4009..2e73318a7bd4 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -208,9 +208,15 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: if __name__ == "__main__": - pass + print("") # # how to execute - # with open("ciphertext.txt") as file: + # with open("out.txt") as file: # ciphertext = file.read() # key = find_key_from_vigenere_cipher(ciphertext) # print(key) + + + +# ---------- TESTS ---------- +# def test_index_of_coincidence(f) + diff --git a/ciphers/tests/test.py b/ciphers/tests/test.py new file mode 100644 index 000000000000..ffc8670fb916 --- /dev/null +++ b/ciphers/tests/test.py @@ -0,0 +1,9 @@ +import unittest +from ciphers.break_vigenere import * + + +class TestBreakVigenere(unittest.TestCase): + def test_index_of_coincidence(self): + dictionary = {'a': 5, 'b': 1, 'c': 4} + ic = index_of_coincidence(dictionary, 100) + self.assertAlmostEqual(ic, 0.0032323232323232) \ No newline at end of file From aca65f87851366906db0be4d05ecef27edc169fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:45:22 +0000 Subject: [PATCH 13/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/break_vigenere.py | 2 -- ciphers/tests/test.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index 2e73318a7bd4..b9121a7cbf65 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -216,7 +216,5 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: # print(key) - # ---------- TESTS ---------- # def test_index_of_coincidence(f) - diff --git a/ciphers/tests/test.py b/ciphers/tests/test.py index ffc8670fb916..8b1de88cd62a 100644 --- a/ciphers/tests/test.py +++ b/ciphers/tests/test.py @@ -4,6 +4,6 @@ class TestBreakVigenere(unittest.TestCase): def test_index_of_coincidence(self): - dictionary = {'a': 5, 'b': 1, 'c': 4} + dictionary = {"a": 5, "b": 1, "c": 4} ic = index_of_coincidence(dictionary, 100) - self.assertAlmostEqual(ic, 0.0032323232323232) \ No newline at end of file + self.assertAlmostEqual(ic, 0.0032323232323232) From f1c953df289be628b10f7c8d569362497716a7c7 Mon Sep 17 00:00:00 2001 From: nikos258 Date: Mon, 8 Sep 2025 20:09:44 +0300 Subject: [PATCH 14/18] made some pytests --- ciphers/break_vigenere.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index 2e73318a7bd4..3e8ad63fad3a 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -1,3 +1,5 @@ +import math + LETTER_FREQUENCIES_DICT = { "A": 8.12, "B": 1.49, @@ -216,7 +218,20 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: # print(key) - # ---------- TESTS ---------- -# def test_index_of_coincidence(f) + +class Test: + def test_index_of_coincidence(self): + ic = index_of_coincidence({'a': 50, 'b': 50}, 50) + assert math.isclose(ic, 2.0) + + def test_calculate_indexes_of_coincidence(self): + ciphertext = "hellothere" + result = calculate_indexes_of_coincidence(ciphertext, 2) + assert result == [0.1, 0.3] + + def test_friedman_method(self): + ciphertext = "asqsfdybpypvhftnboexqumfsnglmcstyefv" + result = friedman_method(ciphertext, 5) + assert result == 3 From f31a4e61a3dd10385afd0d5e75e7810de98b16e9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 17:14:16 +0000 Subject: [PATCH 15/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/break_vigenere.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index 2e73318a7bd4..b9121a7cbf65 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -216,7 +216,5 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: # print(key) - # ---------- TESTS ---------- # def test_index_of_coincidence(f) - From 4c9f04226a30147e6414ee32bca54acb79a05736 Mon Sep 17 00:00:00 2001 From: nikos258 Date: Tue, 9 Sep 2025 14:33:03 +0300 Subject: [PATCH 16/18] made test_break_vigenere.py --- ciphers/break_vigenere.py | 17 ++++++------ ciphers/test_break_vigenere.py | 48 ++++++++++++++++++++++++++++++++++ ciphers/tests/test.py | 9 ------- 3 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 ciphers/test_break_vigenere.py delete mode 100644 ciphers/tests/test.py diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index 2e73318a7bd4..9f2068c9daa6 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -90,7 +90,7 @@ def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int: page: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher The algorithm is in the book "Introduction to Cryptography", K. Draziotis https://repository.kallipos.gr/handle/11419/8183 - :param ciphertext: a string (text) + :param ciphertext: a string (uppercase text) :param max_keylength: the maximum length of key that Friedman's method should check, if None then it defaults to the length of the cipher :return: the length of the key @@ -144,7 +144,7 @@ def find_key(ciphertext: str, key_length: int) -> str: to a letter of the key. The whole procedure takes place for every letter of the key (essentially as many times as the length of the key). See here: https://www.youtube.com/watch?v=LaWp_Kq0cKs - :param ciphertext: a string (text) + :param ciphertext: a string (uppercase text) :param key_length: a supposed length of the key :return: the key as a string """ @@ -190,7 +190,12 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: Tries to find the key length and then the actual key of a Vigenere ciphertext. It uses Friedman's method and statistical analysis. It works best for large pieces of text written in the english language. + IMPORTANT: Some trial and error might be needed to find the actual key + especially by changing the value of MAX_KEYLENGTH. + """ + # clean the ciphertext so that it only contains uppercase letters with no + # punctuation, spaces etc. clean_ciphertext_list = [] for symbol in ciphertext.upper(): if symbol in LETTERS: @@ -208,15 +213,9 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: if __name__ == "__main__": - print("") + print() # # how to execute # with open("out.txt") as file: # ciphertext = file.read() # key = find_key_from_vigenere_cipher(ciphertext) # print(key) - - - -# ---------- TESTS ---------- -# def test_index_of_coincidence(f) - diff --git a/ciphers/test_break_vigenere.py b/ciphers/test_break_vigenere.py new file mode 100644 index 000000000000..7998c9e5282a --- /dev/null +++ b/ciphers/test_break_vigenere.py @@ -0,0 +1,48 @@ +import math + +from ciphers.break_vigenere import ( + LETTER_FREQUENCIES_DICT, + calculate_indexes_of_coincidence, + find_key, + find_key_from_vigenere_cipher, + friedman_method, + get_frequencies, + index_of_coincidence, +) + + +class Test: + def test_index_of_coincidence(self): + ic = index_of_coincidence({"a": 50, "b": 50}, 50) + assert math.isclose(ic, 2.0) + + def test_calculate_indexes_of_coincidence(self): + ciphertext = "hellothere" + result = calculate_indexes_of_coincidence(ciphertext, 2) + assert result == [0.1, 0.3] + + def test_friedman_method(self): + ciphertext = "asqsfdybpypvhftnboexqumfsnglmcstyefv".upper() + result = friedman_method(ciphertext, 5) + assert result == 3 + + def test_get_frequencies(self): + result = get_frequencies() + expected = tuple(num / 100 for num in LETTER_FREQUENCIES_DICT.values()) + assert result == expected + + def test_find_key(self): + ciphertext = "asqsfdybpypvhftnboexqumfsnglmcstyefv".upper() + result = find_key(ciphertext, 3) + assert result == "ABC" + + def test_find_key_from_vigenere_cipher(self): + ciphertext = ( + "A dqxryeocqgj mpth ms sptusb ticq ms aoihv. Fgf " + "edrsou ylxmes jhv, sos exwyon uweqe igu msfjplxj " + "vbtliyy. Bno xme xqupi's b uwele, bpg eql ujh qjn bpg " + "atmfp piwema spfyftv. E wotg ec fnz qwljr ocpi bovng " + "wremn dw xwfgw." + ) + result = find_key_from_vigenere_cipher(ciphertext) + assert result == "ABCDEF" diff --git a/ciphers/tests/test.py b/ciphers/tests/test.py deleted file mode 100644 index 8b1de88cd62a..000000000000 --- a/ciphers/tests/test.py +++ /dev/null @@ -1,9 +0,0 @@ -import unittest -from ciphers.break_vigenere import * - - -class TestBreakVigenere(unittest.TestCase): - def test_index_of_coincidence(self): - dictionary = {"a": 5, "b": 1, "c": 4} - ic = index_of_coincidence(dictionary, 100) - self.assertAlmostEqual(ic, 0.0032323232323232) From ed66361c19ebaa4b85ad5bab5321dfee41206318 Mon Sep 17 00:00:00 2001 From: nikos258 Date: Tue, 9 Sep 2025 14:42:25 +0300 Subject: [PATCH 17/18] fixed some errors --- ciphers/break_vigenere.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index b9121a7cbf65..992e3ebeaf28 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -215,6 +215,3 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: # key = find_key_from_vigenere_cipher(ciphertext) # print(key) - -# ---------- TESTS ---------- -# def test_index_of_coincidence(f) From 5cf3cc3ff84e87ea6c9b10106a8f36b4f8cb9c6f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:43:02 +0000 Subject: [PATCH 18/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ciphers/break_vigenere.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ciphers/break_vigenere.py b/ciphers/break_vigenere.py index 992e3ebeaf28..9cd69e9c9f01 100644 --- a/ciphers/break_vigenere.py +++ b/ciphers/break_vigenere.py @@ -214,4 +214,3 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str: # ciphertext = file.read() # key = find_key_from_vigenere_cipher(ciphertext) # print(key) -

AltStyle によって変換されたページ (->オリジナル) /