44 'N' : 6.95 , 'O' : 7.68 , 'P' : 1.82 , 'Q' : 0.11 , 'R' : 6.02 , 'S' : 6.28 ,
55 'T' : 9.10 , 'U' : 2.88 , 'V' : 1.11 , 'W' : 2.09 , 'X' : 0.17 , 'Y' : 2.11 , 'Z' : 0.07
66}
7-
87LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
8+ PARAMETER = 0.0665 # index of confidence of the entire language (for english 0.0665)
99
1010
1111def index_of_coincidence (frequencies : dict , length : int ) -> float :
@@ -17,7 +17,7 @@ def index_of_coincidence(frequencies: dict, length: int) -> float:
1717 """
1818 index = 0.0
1919 for value in frequencies .values ():
20- index += (value / length )** 2
20+ index += (value / length ) ** 2
2121 return index
2222
2323
@@ -37,7 +37,7 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
3737 for j in range (step ):
3838 frequencies = dict ()
3939 c = 0
40- for i in range (0 + j , length , step ):
40+ for i in range (0 + j , length , step ):
4141 c += 1
4242 try : # in case the frequencies dictionary does not already have this key
4343 frequencies [ciphertext [i ]] += 1
@@ -48,6 +48,40 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
4848 return indexes_of_coincidence
4949
5050
51+ def friedman_method (ciphertext : str , max_keylength : int = None ) -> int :
52+ """
53+ Implements Friedman's method for finding the length of the key of a Vigenere cipher. It finds the length with an
54+ index of confidence closer to that of an average text in the english language.
55+ :param ciphertext: a string (text)
56+ :param max_keylength: the maximum length of key that Friedman's method should check, if None then it defaults to the
57+ length of the cipher
58+ :return: the length of the key
59+ """
60+ # sets the default value of MAX_KEYLEBGTH
61+ if max_keylength is None :
62+ max_keylength = len (ciphertext )
63+
64+ frequencies = [1.5 ] # the zeroth position should not be used: length of key is greater than zero
65+
66+ # for every length of key
67+ for i in range (1 , max_keylength + 1 ):
68+
69+ # for a specific length it finds the minimum index of coincidence
70+ min1 = 15.0
71+ for val in calculate_indexes_of_coincidence (ciphertext , i ):
72+ if abs (val - PARAMETER ) < abs (min1 - PARAMETER ):
73+ min1 = val
74+ frequencies .append (min1 )
75+
76+ # finds which length of key has the minimum difference with the language PARAMETER
77+ li = (15.0 , - 1 ) # initialization
78+ for i in range (len (frequencies )):
79+ if abs (frequencies [i ] - PARAMETER ) < abs (li [0 ] - PARAMETER ):
80+ li = (frequencies [i ], i )
81+
82+ return li [1 ]
83+
84+
5185def find_key_from_vigenere_cipher (ciphertext : str ) -> str :
5286 clean_ciphertext = list ()
5387 for symbol in ciphertext :
@@ -61,4 +95,4 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str:
6195
6296
6397if __name__ == '__main__' :
64- print (index_of_coincidence (LETTER_FREQUENCIES_DICT , 1000 ))
98+ print (index_of_coincidence (LETTER_FREQUENCIES_DICT , 1000 ))
0 commit comments