27
27
"Z" : 0.07 ,
28
28
}
29
29
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
30
- PARAMETER = 0.0665 # index of confidence of the entire language (for english 0.0665)
30
+ PARAMETER = 0.0665 # index of confidence of the entire language (for
31
+ # english 0.0665)
31
32
MAX_KEYLENGTH = (
32
- None # None is the default, you can also try a positive integer (example: 10)
33
+ None # None is the default, you can also try a positive integer (
34
+ # example: 10)
33
35
)
34
36
35
37
36
38
def index_of_coincidence (frequencies : dict , length : int ) -> float :
37
39
"""
38
40
Calculates the index of coincidence for a text.
39
- :param frequencies: dictionary of the form {letter_of_the_alphabet: amount of times it appears in the text as a percentage}
41
+ :param frequencies: dictionary of the form {letter_of_the_alphabet: amount
42
+ of times it appears in the text as a percentage}
40
43
:param length: the length of the text
41
44
:return: the index of coincidence
42
- >>> index_of_coincidence({'A':1,'D':2,'E':3,'F':1,'H':1,'L': 2,'N':1,'T':1,'W':1}, 13)
43
- 0.0641025641025641
45
+ >>> index_of_coincidence({'A':1,'D':2,'E':3,'F':1,'H':1,'L': 2,'N':1,'T':1,'W':1}, 13) 0.0641025641025641
44
46
"""
45
47
index = 0.0
46
48
for value in frequencies .values ():
@@ -50,9 +52,10 @@ def index_of_coincidence(frequencies: dict, length: int) -> float:
50
52
51
53
def calculate_indexes_of_coincidence (ciphertext : str , step : int ) -> list :
52
54
"""
53
- For each number j in the range [0, step) the function checks the letters of the ciphertext whose position has the
54
- form j+n*step, where n is an integer and for these letters it calculates the index of coincidence. It returns a list
55
- with step elements, which represent the indexes of coincidence.
55
+ For each number j in the range [0, step) the function checks the letters of
56
+ the ciphertext whose position has the form j+n*step, where n is an integer
57
+ and for these letters it calculates the index of coincidence. It returns a
58
+ list with step elements, which represent the indexes of coincidence.
56
59
:param ciphertext: s string (text)
57
60
:param step: the step when traversing through the cipher
58
61
:return: a list with the indexes of coincidence
@@ -66,34 +69,39 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
66
69
c = 0
67
70
for i in range (0 + j , length , step ):
68
71
c += 1
69
- try : # in case the frequencies dictionary does not already have this key
72
+ try : # in case the frequencies dictionary does not already have
73
+ # this key
70
74
frequencies [ciphertext [i ]] += 1
71
75
except KeyError :
72
76
frequencies [ciphertext [i ]] = 1
73
- if c > 1 : # to avoid division by zero in the index_of_coincidence function
77
+ if c > 1 : # to avoid division by zero in the index_of_coincidence
78
+ # function
74
79
indexes_of_coincidence .append (index_of_coincidence (frequencies , c ))
75
80
76
81
return indexes_of_coincidence
77
82
78
83
79
84
def friedman_method (ciphertext : str , max_keylength : int | None = None ) -> int :
80
85
"""
81
- Implements Friedman's method for finding the length of the key of a Vigenere cipher. It finds the length with an
82
- index of confidence closer to that of an average text in the english language. Check the wikipedia page:
83
- https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
84
- The algorithm is in the book "Introduction to Cryptography", K. Draziotis https://repository.kallipos.gr/handle/11419/8183
86
+ Implements Friedman's method for finding the length of the key of a
87
+ Vigenere cipher. It finds the length with an index of confidence closer
88
+ to that of an average text in the english language. Check the wikipedia
89
+ page: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher The algorithm
90
+ is in the book "Introduction to Cryptography", K. Draziotis
91
+ https://repository.kallipos.gr/handle/11419/8183
85
92
:param ciphertext: a string (text)
86
- :param max_keylength: the maximum length of key that Friedman's method should check, if None then it defaults to the
87
- length of the cipher
93
+ :param max_keylength: the maximum length of key that Friedman's method
94
+ should check, if None then it defaults to the length of the cipher
88
95
:return: the length of the key
89
96
"""
90
- # sets the default value of MAX_KEYLEBGTH
97
+ # sets the default value of MAX_KEYLENGTH
91
98
if max_keylength is None :
92
99
max_keylength = len (ciphertext )
93
100
94
101
frequencies = [
95
102
1.5
96
- ] # the zeroth position should not be used: length of key is greater than zero
103
+ ] # the zeroth position should not be used: length of key is greater
104
+ # than zero
97
105
98
106
# for every length of key
99
107
for i in range (1 , max_keylength + 1 ):
@@ -104,7 +112,8 @@ def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int:
104
112
min1 = val
105
113
frequencies .append (min1 )
106
114
107
- # finds which length of key has the minimum difference with the language PARAMETER
115
+ # finds which length of key has the minimum difference with the language
116
+ # PARAMETER
108
117
li = (15.0 , - 1 ) # initialization
109
118
for i in range (len (frequencies )):
110
119
if abs (frequencies [i ] - PARAMETER ) < abs (li [0 ] - PARAMETER ):
@@ -114,20 +123,26 @@ def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int:
114
123
115
124
116
125
def get_frequencies () -> tuple :
117
- """Return the values of the global variable @LETTER_FREQUENCIES_DICT as a tuple ex. (0.25, 1.42, ...)."""
126
+ """Return the values of the global variable @LETTER_FREQUENCIES_DICT as a
127
+ tuple ex. (0.25, 1.42, ...).
128
+ """
118
129
t = tuple (LETTER_FREQUENCIES_DICT [chr (i )] for i in range (ord ("A" ), ord ("A" ) + 26 ))
119
130
return tuple (num / 100 for num in t )
120
131
121
132
122
133
def find_key (ciphertext : str , key_length : int ) -> str :
123
134
"""
124
- Finds the key of a text which has been encrypted with the Vigenere algorithm, using statistical analysis.
125
- The function needs an estimation of the length of the key. Firstly it finds the frequencies of the letters in the
126
- text. Then it compares these frequencies with those of an average text in the english language. For each letter it
127
- multiplies its frequency with the average one and adds them all together, then it shifts the frequencies of the text
128
- cyclically by one position and repeats the process. The shift that produces the largest sum corresponds to a letter
129
- of the key. The whole procedure takes place for every letter of the key (essentially as many times as the length
130
- of the key). See here: https://www.youtube.com/watch?v=LaWp_Kq0cKs
135
+ Finds the key of a text which has been encrypted with the Vigenere
136
+ algorithm, using statistical analysis. The function needs an estimation
137
+ of the length of the key. Firstly it finds the frequencies of the
138
+ letters in the text. Then it compares these frequencies with those of an
139
+ average text in the english language. For each letter it multiplies its
140
+ frequency with the average one and adds them all together, then it
141
+ shifts the frequencies of the text cyclically by one position and
142
+ repeats the process. The shift that produces the largest sum corresponds
143
+ to a letter of the key. The whole procedure takes place for every letter
144
+ of the key (essentially as many times as the length of the key). See
145
+ here: https://www.youtube.com/watch?v=LaWp_Kq0cKs
131
146
:param ciphertext: a string (text)
132
147
:param key_length: a supposed length of the key
133
148
:return: the key as a string
@@ -140,8 +155,8 @@ def find_key(ciphertext: str, key_length: int) -> str:
140
155
141
156
# for every letter of the key
142
157
for k in range (key_length ):
143
- # find the frequencies of the letters in the message:
144
- # the frequency of 'A' is in the first position of the freq list and so on
158
+ # find the frequencies of the letters in the message: the frequency
159
+ # of 'A' is in the first position of the freq list and so on
145
160
freq = [0.0 ] * alphabet_length
146
161
c = 0
147
162
for i in range (k , cipher_length , key_length ):
@@ -166,8 +181,9 @@ def find_key(ciphertext: str, key_length: int) -> str:
166
181
167
182
def find_key_from_vigenere_cipher (ciphertext : str ) -> str :
168
183
"""
169
- Tries to find the key length and then the actual key of a Vigenere ciphertext. It uses Friedman's method and
170
- statistical analysis. It works best for large pieces of text written in the english language.
184
+ Tries to find the key length and then the actual key of a Vigenere
185
+ ciphertext. It uses Friedman's method and statistical analysis. It works
186
+ best for large pieces of text written in the english language.
171
187
"""
172
188
clean_ciphertext_list = list ()
173
189
for symbol in ciphertext .upper ():
0 commit comments