4
4
'N' : 6.95 , 'O' : 7.68 , 'P' : 1.82 , 'Q' : 0.11 , 'R' : 6.02 , 'S' : 6.28 ,
5
5
'T' : 9.10 , 'U' : 2.88 , 'V' : 1.11 , 'W' : 2.09 , 'X' : 0.17 , 'Y' : 2.11 , 'Z' : 0.07
6
6
}
7
-
8
7
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
8
+ PARAMETER = 0.0665 # index of confidence of the entire language (for english 0.0665)
9
9
10
10
11
11
def index_of_coincidence (frequencies : dict , length : int ) -> float :
@@ -17,7 +17,7 @@ def index_of_coincidence(frequencies: dict, length: int) -> float:
17
17
"""
18
18
index = 0.0
19
19
for value in frequencies .values ():
20
- index += (value / length )** 2
20
+ index += (value / length ) ** 2
21
21
return index
22
22
23
23
@@ -37,7 +37,7 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
37
37
for j in range (step ):
38
38
frequencies = dict ()
39
39
c = 0
40
- for i in range (0 + j , length , step ):
40
+ for i in range (0 + j , length , step ):
41
41
c += 1
42
42
try : # in case the frequencies dictionary does not already have this key
43
43
frequencies [ciphertext [i ]] += 1
@@ -48,6 +48,40 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
48
48
return indexes_of_coincidence
49
49
50
50
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
+
51
85
def find_key_from_vigenere_cipher (ciphertext : str ) -> str :
52
86
clean_ciphertext = list ()
53
87
for symbol in ciphertext :
@@ -61,4 +95,4 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str:
61
95
62
96
63
97
if __name__ == '__main__' :
64
- print (index_of_coincidence (LETTER_FREQUENCIES_DICT , 1000 ))
98
+ print (index_of_coincidence (LETTER_FREQUENCIES_DICT , 1000 ))
0 commit comments