Skip to content

Commit 50da9d3

Browse files
committed
pre-commit 2
1 parent 8c48e8e commit 50da9d3

File tree

1 file changed

+47
-31
lines changed

1 file changed

+47
-31
lines changed

ciphers/break_vigenere.py

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,22 @@
2727
"Z": 0.07,
2828
}
2929
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)
3132
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)
3335
)
3436

3537

3638
def 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

5153
def 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

7984
def 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

116125
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+
"""
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

122133
def 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

167182
def 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

Comments
 (0)