|
1 | 1 | from __future__ import annotations
|
2 |
| - |
3 | 2 | from string import ascii_letters
|
| 3 | +from typing import Optional, Dict |
4 | 4 |
|
5 | 5 |
|
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: |
7 | 7 | """
|
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. |
12 | 9 |
|
13 | 10 | Parameters:
|
14 | 11 | -----------
|
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. |
22 | 19 |
|
23 | 20 | 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. |
63 | 24 | """
|
64 |
| - # Set default alphabet to lower and upper case english chars |
65 | 25 | alpha = alphabet or ascii_letters
|
66 |
| - |
67 |
| - # The final result string |
68 |
| - result = "" |
| 26 | + result = [] |
69 | 27 |
|
70 | 28 | 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]) |
74 | 33 | 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) |
77 | 35 |
|
78 |
| - # Append the encoded character to the alphabet |
79 |
| - result += alpha[new_key] |
| 36 | + return ''.join(result) |
80 | 37 |
|
81 |
| - return result |
82 | 38 |
|
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: |
85 | 40 | """
|
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. |
89 | 42 |
|
90 | 43 | Parameters:
|
91 | 44 | -----------
|
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. |
99 | 52 |
|
100 | 53 | 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. |
142 | 57 | """
|
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) |
147 | 59 |
|
148 | 60 |
|
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]: |
150 | 62 | """
|
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. |
155 | 64 |
|
156 | 65 | Parameters:
|
157 | 66 | -----------
|
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. |
194 | 77 | """
|
195 |
| - # Set default alphabet to lower and upper case english chars |
196 | 78 | alpha = alphabet or ascii_letters
|
197 |
| - |
198 |
| - # To store data on all the combinations |
199 | 79 | brute_force_data = {}
|
200 | 80 |
|
201 |
| - # Cycle through each combination |
202 | 81 | 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 |
205 | 84 |
|
206 | 85 | return brute_force_data
|
207 | 86 |
|
208 | 87 |
|
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 | + |
210 | 121 | while True:
|
211 | 122 | 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}") |
213 | 125 |
|
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() |
216 | 127 |
|
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": |
221 | 129 | 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}") |
223 | 135 |
|
224 |
| - print(encrypt(input_string, key)) |
225 | 136 | elif choice == "2":
|
226 | 137 | 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}") |
228 | 143 |
|
229 |
| - print(decrypt(input_string, key)) |
230 | 144 | 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 |
233 | 147 |
|
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}") |
236 | 151 |
|
237 | 152 | elif choice == "4":
|
238 | 153 | print("Goodbye.")
|
239 | 154 | break
|
| 155 | + |
| 156 | + else: |
| 157 | + print("Invalid choice, please enter a valid option.") |
| 158 | + |
| 159 | + |
| 160 | +if __name__ == "__main__": |
| 161 | + main() |
0 commit comments