Skip to content

Commit 8c48e8e

Browse files
committed
pre-commit 1
1 parent d8c6d02 commit 8c48e8e

File tree

1 file changed

+46
-19
lines changed

1 file changed

+46
-19
lines changed

ciphers/break_vigenere.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,36 @@
11
LETTER_FREQUENCIES_DICT = {
2-
'A': 8.12, 'B': 1.49, 'C': 2.71, 'D': 4.32, 'E': 12.02, 'F': 2.3, 'G': 2.03,
3-
'H': 5.92, 'I': 7.31, 'J': 0.1, 'K': 0.69, 'L': 3.92, 'M': 2.61,
4-
'N': 6.95, 'O': 7.68, 'P': 1.82, 'Q': 0.11, 'R': 6.02, 'S': 6.28,
5-
'T': 9.10, 'U': 2.88, 'V': 1.11, 'W': 2.09, 'X': 0.17, 'Y': 2.11, 'Z': 0.07
2+
"A": 8.12,
3+
"B": 1.49,
4+
"C": 2.71,
5+
"D": 4.32,
6+
"E": 12.02,
7+
"F": 2.3,
8+
"G": 2.03,
9+
"H": 5.92,
10+
"I": 7.31,
11+
"J": 0.1,
12+
"K": 0.69,
13+
"L": 3.92,
14+
"M": 2.61,
15+
"N": 6.95,
16+
"O": 7.68,
17+
"P": 1.82,
18+
"Q": 0.11,
19+
"R": 6.02,
20+
"S": 6.28,
21+
"T": 9.10,
22+
"U": 2.88,
23+
"V": 1.11,
24+
"W": 2.09,
25+
"X": 0.17,
26+
"Y": 2.11,
27+
"Z": 0.07,
628
}
729
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
830
PARAMETER = 0.0665 # index of confidence of the entire language (for english 0.0665)
9-
MAX_KEYLENGTH = None # None is the default, you can also try a positive integer (example: 10)
31+
MAX_KEYLENGTH = (
32+
None # None is the default, you can also try a positive integer (example: 10)
33+
)
1034

1135

1236
def index_of_coincidence(frequencies: dict, length: int) -> float:
@@ -20,8 +44,8 @@ def index_of_coincidence(frequencies: dict, length: int) -> float:
2044
"""
2145
index = 0.0
2246
for value in frequencies.values():
23-
index += value * (value-1)
24-
return index / (length * (length-1))
47+
index += value * (value - 1)
48+
return index / (length * (length - 1))
2549

2650

2751
def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
@@ -38,7 +62,7 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
3862

3963
# for every starting point in [0, step)
4064
for j in range(step):
41-
frequencies = dict()
65+
frequencies = dict[str, int]
4266
c = 0
4367
for i in range(0 + j, length, step):
4468
c += 1
@@ -52,7 +76,7 @@ def calculate_indexes_of_coincidence(ciphertext: str, step: int) -> list:
5276
return indexes_of_coincidence
5377

5478

55-
def friedman_method(ciphertext: str, max_keylength: int=None) -> int:
79+
def friedman_method(ciphertext: str, max_keylength: int | None = None) -> int:
5680
"""
5781
Implements Friedman's method for finding the length of the key of a Vigenere cipher. It finds the length with an
5882
index of confidence closer to that of an average text in the english language. Check the wikipedia page:
@@ -67,11 +91,12 @@ def friedman_method(ciphertext: str, max_keylength: int=None) -> int:
6791
if max_keylength is None:
6892
max_keylength = len(ciphertext)
6993

70-
frequencies = [1.5] # the zeroth position should not be used: length of key is greater than zero
94+
frequencies = [
95+
1.5
96+
] # the zeroth position should not be used: length of key is greater than zero
7197

7298
# for every length of key
7399
for i in range(1, max_keylength + 1):
74-
75100
# for a specific length it finds the minimum index of coincidence
76101
min1 = 15.0
77102
for val in calculate_indexes_of_coincidence(ciphertext, i):
@@ -90,7 +115,7 @@ def friedman_method(ciphertext: str, max_keylength: int=None) -> int:
90115

91116
def get_frequencies() -> tuple:
92117
"""Return the values of the global variable @LETTER_FREQUENCIES_DICT as a tuple ex. (0.25, 1.42, ...)."""
93-
t = tuple(LETTER_FREQUENCIES_DICT[chr(i)] for i in range(ord('A'), ord('A') + 26))
118+
t = tuple(LETTER_FREQUENCIES_DICT[chr(i)] for i in range(ord("A"), ord("A") + 26))
94119
return tuple(num / 100 for num in t)
95120

96121

@@ -107,7 +132,7 @@ def find_key(ciphertext: str, key_length: int) -> str:
107132
:param key_length: a supposed length of the key
108133
:return: the key as a string
109134
"""
110-
a = ord('A')
135+
a = ord("A")
111136
cipher_length = len(ciphertext)
112137
alphabet_length = 26 # the length of the english alphabet
113138

@@ -117,7 +142,7 @@ def find_key(ciphertext: str, key_length: int) -> str:
117142
for k in range(key_length):
118143
# find the frequencies of the letters in the message:
119144
# the frequency of 'A' is in the first position of the freq list and so on
120-
freq = [0]*alphabet_length
145+
freq = [0.0] * alphabet_length
121146
c = 0
122147
for i in range(k, cipher_length, key_length):
123148
freq[ord(ciphertext[i]) - a] += 1
@@ -131,7 +156,9 @@ def find_key(ciphertext: str, key_length: int) -> str:
131156
new_val = sum((freq[j] * real_freq[j]) for j in range(alphabet_length))
132157
if max1[0] < new_val:
133158
max1 = [new_val, i]
134-
freq.append(freq.pop(0)) # shift the list cyclically one position to the left
159+
freq.append(
160+
freq.pop(0)
161+
) # shift the list cyclically one position to the left
135162
key.append(max1[1])
136163

137164
return "".join(chr(num + a) for num in key) # return the key as a string
@@ -142,12 +169,12 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str:
142169
Tries to find the key length and then the actual key of a Vigenere ciphertext. It uses Friedman's method and
143170
statistical analysis. It works best for large pieces of text written in the english language.
144171
"""
145-
clean_ciphertext = list()
172+
clean_ciphertext_list = list()
146173
for symbol in ciphertext.upper():
147174
if symbol in LETTERS:
148-
clean_ciphertext.append(symbol)
175+
clean_ciphertext_list.append(symbol)
149176

150-
clean_ciphertext = "".join(clean_ciphertext)
177+
clean_ciphertext = "".join(clean_ciphertext_list)
151178

152179
key_length = friedman_method(clean_ciphertext, max_keylength=MAX_KEYLENGTH)
153180
print(f"The length of the key is {key_length}")
@@ -158,7 +185,7 @@ def find_key_from_vigenere_cipher(ciphertext: str) -> str:
158185
return key
159186

160187

161-
if __name__ == '__main__':
188+
if __name__ == "__main__":
162189
c = ""
163190
k = find_key_from_vigenere_cipher(c)
164191
print(k)

0 commit comments

Comments
 (0)