2727 "Z" : 0.07 ,
2828}
2929LETTERS = "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)
3132MAX_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)
3335)
3436
3537
3638def index_of_coincidence (frequencies : dict , length : int ) -> float :
3739 """
3840 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}
4043 :param length: the length of the text
4144 :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
4446 """
4547 index = 0.0
4648 for value in frequencies .values ():
@@ -50,9 +52,10 @@ def index_of_coincidence(frequencies: dict, length: int) -> float:
5052
5153def calculate_indexes_of_coincidence (ciphertext : str , step : int ) -> list :
5254 """
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.
5659 :param ciphertext: s string (text)
5760 :param step: the step when traversing through the cipher
5861 :return: a list with the indexes of coincidence
@@ -66,34 +69,39 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
6669 c = 0
6770 for i in range (0 + j , length , step ):
6871 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
7074 frequencies [ciphertext [i ]] += 1
7175 except KeyError :
7276 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
7479 indexes_of_coincidence .append (index_of_coincidence (frequencies , c ))
7580
7681 return indexes_of_coincidence
7782
7883
7984def friedman_method (ciphertext : str , max_keylength : int | None = None ) -> int :
8085 """
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
8592 :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
8895 :return: the length of the key
8996 """
90- # sets the default value of MAX_KEYLEBGTH
97+ # sets the default value of MAX_KEYLENGTH
9198 if max_keylength is None :
9299 max_keylength = len (ciphertext )
93100
94101 frequencies = [
95102 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
97105
98106 # for every length of key
99107 for i in range (1 , max_keylength + 1 ):
@@ -104,7 +112,8 @@ def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int:
104112 min1 = val
105113 frequencies .append (min1 )
106114
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
108117 li = (15.0 , - 1 ) # initialization
109118 for i in range (len (frequencies )):
110119 if abs (frequencies [i ] - PARAMETER ) < abs (li [0 ] - PARAMETER ):
@@ -114,20 +123,26 @@ def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int:
114123
115124
116125def 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+ """
118129 t = tuple (LETTER_FREQUENCIES_DICT [chr (i )] for i in range (ord ("A" ), ord ("A" ) + 26 ))
119130 return tuple (num / 100 for num in t )
120131
121132
122133def find_key (ciphertext : str , key_length : int ) -> str :
123134 """
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
131146 :param ciphertext: a string (text)
132147 :param key_length: a supposed length of the key
133148 :return: the key as a string
@@ -140,8 +155,8 @@ def find_key(ciphertext: str, key_length: int) -> str:
140155
141156 # for every letter of the key
142157 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
145160 freq = [0.0 ] * alphabet_length
146161 c = 0
147162 for i in range (k , cipher_length , key_length ):
@@ -166,8 +181,9 @@ def find_key(ciphertext: str, key_length: int) -> str:
166181
167182def find_key_from_vigenere_cipher (ciphertext : str ) -> str :
168183 """
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.
171187 """
172188 clean_ciphertext_list = list ()
173189 for symbol in ciphertext .upper ():
0 commit comments