Skip to content

Commit 3a93481

Browse files
authored
Update caesar_cipher.py
1 parent 03a4251 commit 3a93481

File tree

1 file changed

+106
-184
lines changed

1 file changed

+106
-184
lines changed

ciphers/caesar_cipher.py

+106-184
Original file line numberDiff line numberDiff line change
@@ -1,239 +1,161 @@
11
from __future__ import annotations
2-
32
from string import ascii_letters
3+
from typing import Optional, Dict
44

55

6-
def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
6+
def encrypt(input_string: str, key: int, alphabet: Optional[str] = None) -> str:
77
"""
8-
encrypt
9-
=======
10-
Encodes a given string with the caesar cipher and returns the encoded
11-
message
8+
Encodes a given string using the Caesar cipher and returns the encoded message.
129
1310
Parameters:
1411
-----------
15-
* input_string: the plain-text that needs to be encoded
16-
* key: the number of letters to shift the message by
17-
18-
Optional:
19-
* alphabet (None): the alphabet used to encode the cipher, if not
20-
specified, the standard english alphabet with upper and lowercase
21-
letters is used
12+
input_string : str
13+
The plain text that needs to be encoded.
14+
key : int
15+
The number of letters to shift the message by.
16+
alphabet : Optional[str]
17+
The alphabet used to encode the cipher. If not specified, the standard English
18+
alphabet with upper and lowercase letters is used.
2219
2320
Returns:
24-
* A string containing the encoded cipher-text
25-
26-
More on the caesar cipher
27-
=========================
28-
The caesar cipher is named after Julius Caesar who used it when sending
29-
secret military messages to his troops. This is a simple substitution cipher
30-
where every character in the plain-text is shifted by a certain number known
31-
as the "key" or "shift".
32-
33-
Example:
34-
Say we have the following message:
35-
"Hello, captain"
36-
37-
And our alphabet is made up of lower and uppercase letters:
38-
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
39-
40-
And our shift is "2"
41-
42-
We can then encode the message, one letter at a time. "H" would become "J",
43-
since "J" is two letters away, and so on. If the shift is ever two large, or
44-
our letter is at the end of the alphabet, we just start at the beginning
45-
("Z" would shift to "a" then "b" and so on).
46-
47-
Our final message would be "Jgnnq, ecrvckp"
48-
49-
Further reading
50-
===============
51-
* https://en.m.wikipedia.org/wiki/Caesar_cipher
52-
53-
Doctests
54-
========
55-
>>> encrypt('The quick brown fox jumps over the lazy dog', 8)
56-
'bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo'
57-
58-
>>> encrypt('A very large key', 8000)
59-
's nWjq dSjYW cWq'
60-
61-
>>> encrypt('a lowercase alphabet', 5, 'abcdefghijklmnopqrstuvwxyz')
62-
'f qtbjwhfxj fqumfgjy'
21+
--------
22+
str
23+
A string containing the encoded cipher text.
6324
"""
64-
# Set default alphabet to lower and upper case english chars
6525
alpha = alphabet or ascii_letters
66-
67-
# The final result string
68-
result = ""
26+
result = []
6927

7028
for character in input_string:
71-
if character not in alpha:
72-
# Append without encryption if character is not in the alphabet
73-
result += character
29+
if character in alpha:
30+
index = alpha.index(character)
31+
new_index = (index + key) % len(alpha)
32+
result.append(alpha[new_index])
7433
else:
75-
# Get the index of the new key and make sure it isn't too large
76-
new_key = (alpha.index(character) + key) % len(alpha)
34+
result.append(character)
7735

78-
# Append the encoded character to the alphabet
79-
result += alpha[new_key]
36+
return ''.join(result)
8037

81-
return result
8238

83-
84-
def decrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
39+
def decrypt(input_string: str, key: int, alphabet: Optional[str] = None) -> str:
8540
"""
86-
decrypt
87-
=======
88-
Decodes a given string of cipher-text and returns the decoded plain-text
41+
Decodes a given cipher text using the Caesar cipher and returns the decoded plain text.
8942
9043
Parameters:
9144
-----------
92-
* input_string: the cipher-text that needs to be decoded
93-
* key: the number of letters to shift the message backwards by to decode
94-
95-
Optional:
96-
* alphabet (None): the alphabet used to decode the cipher, if not
97-
specified, the standard english alphabet with upper and lowercase
98-
letters is used
45+
input_string : str
46+
The cipher text that needs to be decoded.
47+
key : int
48+
The number of letters to shift the message backwards to decode.
49+
alphabet : Optional[str]
50+
The alphabet used to decode the cipher. If not specified, the standard English
51+
alphabet with upper and lowercase letters is used.
9952
10053
Returns:
101-
* A string containing the decoded plain-text
102-
103-
More on the caesar cipher
104-
=========================
105-
The caesar cipher is named after Julius Caesar who used it when sending
106-
secret military messages to his troops. This is a simple substitution cipher
107-
where very character in the plain-text is shifted by a certain number known
108-
as the "key" or "shift". Please keep in mind, here we will be focused on
109-
decryption.
110-
111-
Example:
112-
Say we have the following cipher-text:
113-
"Jgnnq, ecrvckp"
114-
115-
And our alphabet is made up of lower and uppercase letters:
116-
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
117-
118-
And our shift is "2"
119-
120-
To decode the message, we would do the same thing as encoding, but in
121-
reverse. The first letter, "J" would become "H" (remember: we are decoding)
122-
because "H" is two letters in reverse (to the left) of "J". We would
123-
continue doing this. A letter like "a" would shift back to the end of
124-
the alphabet, and would become "Z" or "Y" and so on.
125-
126-
Our final message would be "Hello, captain"
127-
128-
Further reading
129-
===============
130-
* https://en.m.wikipedia.org/wiki/Caesar_cipher
131-
132-
Doctests
133-
========
134-
>>> decrypt('bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo', 8)
135-
'The quick brown fox jumps over the lazy dog'
136-
137-
>>> decrypt('s nWjq dSjYW cWq', 8000)
138-
'A very large key'
139-
140-
>>> decrypt('f qtbjwhfxj fqumfgjy', 5, 'abcdefghijklmnopqrstuvwxyz')
141-
'a lowercase alphabet'
54+
--------
55+
str
56+
A string containing the decoded plain text.
14257
"""
143-
# Turn on decode mode by making the key negative
144-
key *= -1
145-
146-
return encrypt(input_string, key, alphabet)
58+
return encrypt(input_string, -key, alphabet)
14759

14860

149-
def brute_force(input_string: str, alphabet: str | None = None) -> dict[int, str]:
61+
def brute_force(input_string: str, alphabet: Optional[str] = None) -> Dict[int, str]:
15062
"""
151-
brute_force
152-
===========
153-
Returns all the possible combinations of keys and the decoded strings in the
154-
form of a dictionary
63+
Returns all possible combinations of keys and the decoded strings as a dictionary.
15564
15665
Parameters:
15766
-----------
158-
* input_string: the cipher-text that needs to be used during brute-force
159-
160-
Optional:
161-
* alphabet: (None): the alphabet used to decode the cipher, if not
162-
specified, the standard english alphabet with upper and lowercase
163-
letters is used
164-
165-
More about brute force
166-
======================
167-
Brute force is when a person intercepts a message or password, not knowing
168-
the key and tries every single combination. This is easy with the caesar
169-
cipher since there are only all the letters in the alphabet. The more
170-
complex the cipher, the larger amount of time it will take to do brute force
171-
172-
Ex:
173-
Say we have a 5 letter alphabet (abcde), for simplicity and we intercepted the
174-
following message:
175-
176-
"dbc"
177-
178-
we could then just write out every combination:
179-
ecd... and so on, until we reach a combination that makes sense:
180-
"cab"
181-
182-
Further reading
183-
===============
184-
* https://en.wikipedia.org/wiki/Brute_force
185-
186-
Doctests
187-
========
188-
>>> brute_force("jFyuMy xIH'N vLONy zILwy Gy!")[20]
189-
"Please don't brute force me!"
190-
191-
>>> brute_force(1)
192-
Traceback (most recent call last):
193-
TypeError: 'int' object is not iterable
67+
input_string : str
68+
The cipher text that needs to be used during brute-force.
69+
alphabet : Optional[str]
70+
The alphabet used to decode the cipher. If not specified, the standard English
71+
alphabet with upper and lowercase letters is used.
72+
73+
Returns:
74+
--------
75+
Dict[int, str]
76+
A dictionary where keys are the shift values and values are the decoded messages.
19477
"""
195-
# Set default alphabet to lower and upper case english chars
19678
alpha = alphabet or ascii_letters
197-
198-
# To store data on all the combinations
19979
brute_force_data = {}
20080

201-
# Cycle through each combination
20281
for key in range(1, len(alpha) + 1):
203-
# Decrypt the message and store the result in the data
204-
brute_force_data[key] = decrypt(input_string, key, alpha)
82+
decoded_message = decrypt(input_string, key, alpha)
83+
brute_force_data[key] = decoded_message
20584

20685
return brute_force_data
20786

20887

209-
if __name__ == "__main__":
88+
def get_valid_integer(prompt: str) -> int:
89+
"""
90+
Prompts the user for a valid integer input.
91+
92+
Parameters:
93+
-----------
94+
prompt : str
95+
The message displayed to the user.
96+
97+
Returns:
98+
--------
99+
int
100+
The validated integer input from the user.
101+
"""
102+
while True:
103+
user_input = input(prompt).strip()
104+
try:
105+
return int(user_input)
106+
except ValueError:
107+
print("Invalid input. Please enter a valid integer.")
108+
109+
110+
def main():
111+
"""
112+
Main function to run the Caesar cipher program with a user-interactive menu.
113+
"""
114+
menu_options = {
115+
"1": "Encrypt",
116+
"2": "Decrypt",
117+
"3": "Brute Force",
118+
"4": "Quit"
119+
}
120+
210121
while True:
211122
print(f'\n{"-" * 10}\n Menu\n{"-" * 10}')
212-
print(*["1.Encrypt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n")
123+
for option, description in menu_options.items():
124+
print(f"{option}. {description}")
213125

214-
# get user input
215-
choice = input("\nWhat would you like to do?: ").strip() or "4"
126+
choice = input("\nWhat would you like to do?: ").strip()
216127

217-
# run functions based on what the user chose
218-
if choice not in ("1", "2", "3", "4"):
219-
print("Invalid choice, please enter a valid choice")
220-
elif choice == "1":
128+
if choice == "1":
221129
input_string = input("Please enter the string to be encrypted: ")
222-
key = int(input("Please enter off-set: ").strip())
130+
key = get_valid_integer("Please enter the offset: ")
131+
alphabet = input("Enter the alphabet to use (leave blank for default): ") or None
132+
133+
encrypted_message = encrypt(input_string, key, alphabet)
134+
print(f"Encrypted message: {encrypted_message}")
223135

224-
print(encrypt(input_string, key))
225136
elif choice == "2":
226137
input_string = input("Please enter the string to be decrypted: ")
227-
key = int(input("Please enter off-set: ").strip())
138+
key = get_valid_integer("Please enter the offset: ")
139+
alphabet = input("Enter the alphabet to use (leave blank for default): ") or None
140+
141+
decrypted_message = decrypt(input_string, key, alphabet)
142+
print(f"Decrypted message: {decrypted_message}")
228143

229-
print(decrypt(input_string, key))
230144
elif choice == "3":
231-
input_string = input("Please enter the string to be decrypted: ")
232-
brute_force_data = brute_force(input_string)
145+
input_string = input("Please enter the string to be brute-forced: ")
146+
alphabet = input("Enter the alphabet to use (leave blank for default): ") or None
233147

234-
for key, value in brute_force_data.items():
235-
print(f"Key: {key} | Message: {value}")
148+
brute_force_data = brute_force(input_string, alphabet)
149+
for key, message in brute_force_data.items():
150+
print(f"Key: {key} | Decoded Message: {message}")
236151

237152
elif choice == "4":
238153
print("Goodbye.")
239154
break
155+
156+
else:
157+
print("Invalid choice, please enter a valid option.")
158+
159+
160+
if __name__ == "__main__":
161+
main()

0 commit comments

Comments
 (0)