11LETTER_FREQUENCIES_DICT = {
2- 'A' : 8.12 , 'B' : 1.49 , 'C' : 2.71 , 'D' : 4.32 , 'E' : 12.02 , 'F' : 2.3 , 'G' : 2.03 ,
3- 'H' : 5.92 , 'I' : 7.31 , 'J' : 0.1 , 'K' : 0.69 , 'L' : 3.92 , 'M' : 2.61 ,
4- 'N' : 6.95 , 'O' : 7.68 , 'P' : 1.82 , 'Q' : 0.11 , 'R' : 6.02 , 'S' : 6.28 ,
5- 'T' : 9.10 , 'U' : 2.88 , 'V' : 1.11 , 'W' : 2.09 , 'X' : 0.17 , 'Y' : 2.11 , 'Z' : 0.07
2+ "A" : 8.12 ,
3+ "B" : 1.49 ,
4+ "C" : 2.71 ,
5+ "D" : 4.32 ,
6+ "E" : 12.02 ,
7+ "F" : 2.3 ,
8+ "G" : 2.03 ,
9+ "H" : 5.92 ,
10+ "I" : 7.31 ,
11+ "J" : 0.1 ,
12+ "K" : 0.69 ,
13+ "L" : 3.92 ,
14+ "M" : 2.61 ,
15+ "N" : 6.95 ,
16+ "O" : 7.68 ,
17+ "P" : 1.82 ,
18+ "Q" : 0.11 ,
19+ "R" : 6.02 ,
20+ "S" : 6.28 ,
21+ "T" : 9.10 ,
22+ "U" : 2.88 ,
23+ "V" : 1.11 ,
24+ "W" : 2.09 ,
25+ "X" : 0.17 ,
26+ "Y" : 2.11 ,
27+ "Z" : 0.07 ,
628}
729LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
830PARAMETER = 0.0665 # index of confidence of the entire language (for english 0.0665)
9- MAX_KEYLENGTH = None # None is the default, you can also try a positive integer (example: 10)
31+ MAX_KEYLENGTH = (
32+ None # None is the default, you can also try a positive integer (example: 10)
33+ )
1034
1135
1236def index_of_coincidence (frequencies : dict , length : int ) -> float :
@@ -20,8 +44,8 @@ def index_of_coincidence(frequencies: dict, length: int) -> float:
2044 """
2145 index = 0.0
2246 for value in frequencies .values ():
23- index += value * (value - 1 )
24- return index / (length * (length - 1 ))
47+ index += value * (value - 1 )
48+ return index / (length * (length - 1 ))
2549
2650
2751def calculate_indexes_of_coincidence (ciphertext : str , step : int ) -> list :
@@ -38,7 +62,7 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
3862
3963 # for every starting point in [0, step)
4064 for j in range (step ):
41- frequencies = dict ()
65+ frequencies = dict [ str , int ]
4266 c = 0
4367 for i in range (0 + j , length , step ):
4468 c += 1
@@ -52,7 +76,7 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
5276 return indexes_of_coincidence
5377
5478
55- def friedman_method (ciphertext : str , max_keylength : int = None ) -> int :
79+ def friedman_method (ciphertext : str , max_keylength : int | None = None ) -> int :
5680 """
5781 Implements Friedman's method for finding the length of the key of a Vigenere cipher. It finds the length with an
5882 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:
6791 if max_keylength is None :
6892 max_keylength = len (ciphertext )
6993
70- frequencies = [1.5 ] # the zeroth position should not be used: length of key is greater than zero
94+ frequencies = [
95+ 1.5
96+ ] # the zeroth position should not be used: length of key is greater than zero
7197
7298 # for every length of key
7399 for i in range (1 , max_keylength + 1 ):
74-
75100 # for a specific length it finds the minimum index of coincidence
76101 min1 = 15.0
77102 for val in calculate_indexes_of_coincidence (ciphertext , i ):
@@ -90,7 +115,7 @@ def friedman_method(ciphertext: str, max_keylength: int=None) -> int:
90115
91116def get_frequencies () -> tuple :
92117 """Return the values of the global variable @LETTER_FREQUENCIES_DICT as a tuple ex. (0.25, 1.42, ...)."""
93- t = tuple (LETTER_FREQUENCIES_DICT [chr (i )] for i in range (ord ('A' ), ord ('A' ) + 26 ))
118+ t = tuple (LETTER_FREQUENCIES_DICT [chr (i )] for i in range (ord ("A" ), ord ("A" ) + 26 ))
94119 return tuple (num / 100 for num in t )
95120
96121
@@ -107,7 +132,7 @@ def find_key(ciphertext: str, key_length: int) -> str:
107132 :param key_length: a supposed length of the key
108133 :return: the key as a string
109134 """
110- a = ord ('A' )
135+ a = ord ("A" )
111136 cipher_length = len (ciphertext )
112137 alphabet_length = 26 # the length of the english alphabet
113138
@@ -117,7 +142,7 @@ def find_key(ciphertext: str, key_length: int) -> str:
117142 for k in range (key_length ):
118143 # find the frequencies of the letters in the message:
119144 # the frequency of 'A' is in the first position of the freq list and so on
120- freq = [0 ] * alphabet_length
145+ freq = [0.0 ] * alphabet_length
121146 c = 0
122147 for i in range (k , cipher_length , key_length ):
123148 freq [ord (ciphertext [i ]) - a ] += 1
@@ -131,7 +156,9 @@ def find_key(ciphertext: str, key_length: int) -> str:
131156 new_val = sum ((freq [j ] * real_freq [j ]) for j in range (alphabet_length ))
132157 if max1 [0 ] < new_val :
133158 max1 = [new_val , i ]
134- freq .append (freq .pop (0 )) # shift the list cyclically one position to the left
159+ freq .append (
160+ freq .pop (0 )
161+ ) # shift the list cyclically one position to the left
135162 key .append (max1 [1 ])
136163
137164 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:
142169 Tries to find the key length and then the actual key of a Vigenere ciphertext. It uses Friedman's method and
143170 statistical analysis. It works best for large pieces of text written in the english language.
144171 """
145- clean_ciphertext = list ()
172+ clean_ciphertext_list = list ()
146173 for symbol in ciphertext .upper ():
147174 if symbol in LETTERS :
148- clean_ciphertext .append (symbol )
175+ clean_ciphertext_list .append (symbol )
149176
150- clean_ciphertext = "" .join (clean_ciphertext )
177+ clean_ciphertext = "" .join (clean_ciphertext_list )
151178
152179 key_length = friedman_method (clean_ciphertext , max_keylength = MAX_KEYLENGTH )
153180 print (f"The length of the key is { key_length } " )
@@ -158,7 +185,7 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str:
158185 return key
159186
160187
161- if __name__ == ' __main__' :
188+ if __name__ == " __main__" :
162189 c = ""
163190 k = find_key_from_vigenere_cipher (c )
164191 print (k )
0 commit comments