1 | #!/usr/bin/env python3 |
||
2 | |||
3 | # mersad/classical/route_cipher.py |
||
4 | # |
||
5 | # This file is a part of: |
||
6 | # Azadeh Afzar - Mersad Cryptography Library in Python language (AA-MCLpy). |
||
7 | # |
||
8 | # Copyright (C) 2019 Azadeh Afzar |
||
9 | # Copyright (C) 2019 Mohammad Mahdi Baghbani Pourvahid |
||
10 | # |
||
11 | # GNU AFFERO GENERAL PUBLIC LICENSE |
||
12 | # |
||
13 | # This program is free software; you can redistribute it and/or modify |
||
14 | # it under the terms of the GNU Affero General Public License as published by |
||
15 | # the Free Software Foundation; either version 3 of the License, or (at |
||
16 | # your option) any later version. |
||
17 | # |
||
18 | # This program is distributed in the hope that it will be useful, but |
||
19 | # WITHOUT ANY WARRANTY; without even the implied warranty of |
||
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
21 | # Affero General Public License for more details. |
||
22 | # |
||
23 | # You should have received a copy of the GNU Affero General Public License |
||
24 | # along with this program; if not, write to the Free Software |
||
25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
26 | # |
||
27 | # ZLIB LICENSE |
||
28 | # |
||
29 | # Permission is granted to anyone to use this software for any purpose, |
||
30 | # including commercial applications, and to alter it and redistribute it |
||
31 | # freely, subject to the following restrictions: |
||
32 | # |
||
33 | # 1. The origin of this software must not be misrepresented; you must not |
||
34 | # claim that you wrote the original software. If you use this software |
||
35 | # in a product, an acknowledgement in the product documentation would be |
||
36 | # appreciated but is not required. |
||
37 | # |
||
38 | # 2. Altered source versions must be plainly marked as such, and must not be |
||
39 | # misrepresented as being the original software. |
||
40 | # |
||
41 | # 3. This notice may not be removed or altered from any source distribution. |
||
42 | # |
||
43 | |||
44 | """ |
||
45 | Azadeh Afzar - Mersad Cryptography Library. |
||
46 | |||
47 | mersad.classical.route_cipher module. |
||
48 | ====================================== |
||
49 | |||
50 | The route is a transposition cipher where the key |
||
51 | is which route to follow when reading the cipher |
||
52 | text from the block created with the plaintext. |
||
53 | |||
54 | The plaintext is written in a grid and then |
||
55 | read off following the route chosen. |
||
56 | |||
57 | """ |
||
58 | |||
59 | # Python Standard Library |
||
60 | import argparse |
||
61 | import sys |
||
62 | from typing import Dict |
||
63 | from typing import List |
||
64 | from typing import Tuple |
||
65 | |||
66 | # Mersad Library |
||
67 | from mersad.util import type_check |
||
68 | from mersad.util.base_class import KWARGS_TYPE |
||
69 | from mersad.util.base_class import MersadClassicalBase |
||
70 | from mersad.util.terminal_app_tools import MainFunctionClassical |
||
71 | from mersad.util.terminal_app_tools import monoalphabetic_common_parser |
||
72 | |||
73 | |||
74 | def main(argv: Tuple[str] = tuple(sys.argv[1:])) -> None: |
||
75 | """Execute program in terminal (cli application).""" |
||
76 | # module descriptions. |
||
77 | description: str = "Azadeh Afzar - Mersad Route Cipher\n" |
||
78 | description += "Encrypt/Decrypt data with Route algorithm." |
||
79 | epilog: str = "This one is really a funny one! very good cipher. NOT SAFE!" |
||
80 | |||
81 | # create a parser and parse command line arguments. |
||
82 | program = RouteCipherMainFunction( |
||
83 | list(argv), RouteCipher, description, epilog, monoalphabetic_common_parser() |
||
84 | ) |
||
85 | program.process() |
||
86 | |||
87 | |||
88 | class RouteCipher(MersadClassicalBase): |
||
89 | r""" |
||
90 | Route cipher algorithm class. |
||
91 | |||
92 | Create an instance like every other python class. |
||
93 | |||
94 | Example: |
||
95 | ================================== |
||
96 | |||
97 | >>> agent = RouteCipher(key=4) |
||
98 | >>> route = [3, 2, 1, 0, 4, 8, 12, 13, 14, 15, 11, 7, 6, 5, 9, 10] |
||
99 | >>> # encrypt a string (WE ARE DISCOVERED). |
||
100 | >>> agent.encrypt("WEAREDISCOVERED", route=route) |
||
101 | 'RAEWECREDXESIDOV' |
||
102 | |||
103 | ================================== |
||
104 | |||
105 | RouteCipher takes keyword arguments when initializing new instance. |
||
106 | |||
107 | Valid kwargs keys are listed in below: |
||
108 | - key : (optional) key for setting up a grid. |
||
109 | - fill : (optional) letter to fill empty spaces in grid. |
||
110 | - route : (optional) the list of indexes for routing through |
||
111 | the plain text or cipher text. |
||
112 | |||
113 | Default key is set to None. it will cause error on encrypt/decrypt |
||
114 | if you don't config it via config() method. |
||
115 | Default fill is set to "X". |
||
116 | Default route is set to None. it will cause error on encrypt/decrypt |
||
117 | if you don't config it via config() method. |
||
118 | |||
119 | Each instance has it's own unique configurations saved in |
||
120 | self.configuration and can work independent from other instances. |
||
121 | |||
122 | Configurations can be changed even after initialization via |
||
123 | self.config() method. |
||
124 | |||
125 | Example: |
||
126 | ================================== |
||
127 | |||
128 | >>> # create object without any keyword arguments so agent will use |
||
129 | >>> # default values for all the settings. |
||
130 | >>> agent = RouteCipher() |
||
131 | >>> # override defaults. |
||
132 | >>> agent.config(key=4, route=[3, 2, 1, 0, 4, 8, 12, 13, |
||
133 | >>> 14, 15, 11, 7, 6, 5, 9, 10]) |
||
134 | >>> # encrypt a string (WE ARE DISCOVERED). |
||
135 | >>> agent.encrypt("WEAREDISCOVERED") |
||
136 | 'RAEWECREDXESIDOV' |
||
137 | |||
138 | ================================== |
||
139 | """ |
||
140 | |||
141 | def _init_subroutines(self) -> None: |
||
142 | """Extend _defaults dictionary with default value for fill.""" |
||
143 | self._defaults["fill"] = "X" |
||
144 | |||
145 | def _config_subroutines(self, **kwargs: KWARGS_TYPE) -> None: |
||
146 | """ |
||
147 | Assign values to self.configuration dictionary. |
||
148 | |||
149 | :raise ValueError: if type of a dictionary value is wrong. |
||
150 | """ |
||
151 | if "key" in kwargs and kwargs["key"] is not None: |
||
152 | type_check.type_guard(kwargs["key"], int) |
||
153 | self.configuration["key"] = kwargs["key"] |
||
154 | if "fill" in kwargs: |
||
155 | type_check.type_guard(kwargs["fill"], str) |
||
156 | self.configuration["fill"] = kwargs["fill"] |
||
157 | if "route" in kwargs: |
||
158 | type_check.type_guard(kwargs["route"], list) |
||
159 | self.configuration["route"] = kwargs["route"] |
||
160 | |||
161 | def _process_subroutines( |
||
162 | self, configurations: Dict[str, KWARGS_TYPE], **kwargs: KWARGS_TYPE |
||
163 | ) -> Dict[str, KWARGS_TYPE]: |
||
164 | """ |
||
165 | Extend functionality of encrypt and decrypt to accept route lists. |
||
166 | |||
167 | :raise ValueError: if type of a dictionary value is wrong. |
||
168 | """ |
||
169 | # flags are all set to False. |
||
170 | flag_route: bool = False |
||
171 | flag_replace_route: bool = False |
||
172 | |||
173 | # get route from kwargs if provided. |
||
174 | if "route" in kwargs: |
||
175 | route: List[int] = kwargs["route"] |
||
176 | type_check.type_guard(route, list) |
||
177 | flag_route = True |
||
178 | |||
179 | # get replace route bool from kwargs if provided. |
||
180 | if "replace_route" in kwargs: |
||
181 | replace_route: bool = kwargs["replace_route"] |
||
182 | type_check.type_guard(replace_route, bool) |
||
183 | flag_replace_route = True |
||
184 | |||
185 | # if there is a route list, proceed to next steps. |
||
186 | if flag_route: |
||
187 | if flag_replace_route: |
||
188 | # save given route list in configuration dict. |
||
189 | self.config(route=route) |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
190 | # replace route in configurations. |
||
191 | configurations["route"] = route |
||
192 | |||
193 | return configurations |
||
194 | |||
195 | @staticmethod |
||
196 | def _translator(text: str, **kwargs: KWARGS_TYPE) -> str: |
||
197 | """ |
||
198 | Wrap the actual encryption/decryption function for class. |
||
199 | |||
200 | :param text : string to be translated. |
||
201 | :return : translated text |
||
202 | :rtype : str |
||
203 | """ |
||
204 | return route_cipher_translator(text, **kwargs) |
||
205 | |||
206 | |||
207 | def route_cipher_translator(text: str, **kwargs: KWARGS_TYPE) -> str: |
||
208 | """ |
||
209 | Translate a string with Route cipher algorithm. |
||
210 | |||
211 | :param text : string to be translated. |
||
212 | :param kwargs: |
||
213 | key : key for encrypt/decrypt, it defines |
||
214 | number of columns. |
||
215 | fill: : character to fill empty spaces in grid. |
||
216 | default is "X". |
||
217 | route: : route to read from. |
||
218 | a list with indexes of grid, ordered |
||
219 | with desired route path, index starts |
||
220 | from 0 from top left square and from |
||
221 | left cell of grid in each new row. |
||
222 | |||
223 | example of indexing grids: |
||
224 | ------------------------------------- |
||
225 | | 0 | 1 | 2 | 3 | 4 | 5 | |
||
226 | ------------------------------------- |
||
227 | | 6 | 7 | 8 | 9 | 10 | 11 | |
||
228 | ------------------------------------- |
||
229 | |||
230 | for encrypting the message: |
||
231 | "go for the left" |
||
232 | with route [0, 6, 7, 1, 2, 8, 9, 3, 4, |
||
233 | 10, 5, 11] |
||
234 | |||
235 | ------------------------------------- |
||
236 | | g | o | f | o | r | t | |
||
237 | ------------------------------------- |
||
238 | | h | e | l | e | f | t | |
||
239 | ------------------------------------- |
||
240 | |||
241 | we get: gheofleofrtt as cipher text. |
||
242 | |||
243 | "note that in this example spaces |
||
244 | were removed but this isn't the case in |
||
245 | actual algorithm and all characters |
||
246 | will be preserved." |
||
247 | length of rout list must be equal to |
||
248 | key * quotient(string length / key). |
||
249 | if the above division has reminder, |
||
250 | add 1 to quotient and then multiply. |
||
251 | |||
252 | decrypt (optional)(default = False) : switch for encryption/decryption mode. |
||
253 | :return : translated text |
||
254 | :rtype : str |
||
255 | """ |
||
256 | # for sake of readability and prettifying below code |
||
257 | # I will assign aliases for key, values inside kwargs. |
||
258 | key: int = kwargs["key"] |
||
259 | if key is None: |
||
260 | raise ValueError("ERROR: key not found, use config method to define a key.") |
||
261 | # fill character. |
||
262 | fill: str = kwargs["fill"] if "fill" in kwargs and kwargs["fill"] else "X" |
||
263 | if key is None: |
||
264 | raise ValueError("ERROR: key not found, use config method to define a key.") |
||
265 | # route path indexes. |
||
266 | route: List[int] = kwargs["route"] |
||
267 | # check rout indexes to be unique by comparing its length to set length. |
||
268 | if len(route) != len(set(route)): |
||
269 | raise ValueError("ERROR: route indexes must be unique.") |
||
270 | # default decrypt to False if no decrypt is defined in kwargs. |
||
271 | decrypt: bool = kwargs["decrypt"] if "decrypt" in kwargs else False |
||
272 | |||
273 | # store length of string. |
||
274 | # length of string is needed for mathematical calculations. |
||
275 | string_length: int = len(text) |
||
276 | # get quotient and remainder of string length to key. |
||
277 | quotient, remainder = divmod(string_length, key) |
||
278 | |||
279 | # rout length must be equal to number of cells in a 2D grid of quotient x key. |
||
280 | # if remainder is not zero, the grid becomes (quotient + 1) x key. |
||
281 | quotient: int = quotient if remainder == 0 else quotient + 1 |
||
282 | if len(route) != (quotient * key): |
||
283 | raise ValueError("ERROR: rout length is wrong.") |
||
284 | |||
285 | if decrypt: |
||
286 | # create a 2D grid of size quotient x key [note that quotient may have been |
||
287 | # modified in last block of code, here we use the modified one]. |
||
288 | grid: List[str] = ["" for i in range(quotient * key)] |
||
289 | # place string letters in grid with respect of route index. |
||
290 | for i, j in enumerate(route): |
||
291 | grid[j] = text[i] |
||
292 | translated: str = "".join(grid) |
||
293 | else: |
||
294 | # if remainder is not zero, add fill characters to the end of string |
||
295 | # so that the remainder become zero. |
||
296 | if remainder != 0: |
||
297 | # turn string into list of letters. |
||
298 | lst: List[str] = list(text) |
||
299 | # add enough fill characters to end of list. |
||
300 | lst.extend([fill] * (key - remainder)) |
||
301 | # create new string. |
||
302 | text = "".join(lst) |
||
303 | # encrypt based on the route. |
||
304 | translated: str = "".join([text[index] for index in route]) |
||
305 | |||
306 | return translated |
||
307 | |||
308 | |||
309 | class RouteCipherMainFunction(MainFunctionClassical): |
||
310 | """Manage Route cipher programs execution from terminal.""" |
||
311 | |||
312 | def _config_agent(self, agent, args: argparse.Namespace) -> None: |
||
313 | """Config the agent parameters in process method.""" |
||
314 | agent.config( |
||
315 | key=args.key, fill=args.fill, route=args.route, |
||
316 | ) |
||
317 | |||
318 | def _custom_arguments(self) -> argparse.ArgumentParser: |
||
319 | """ |
||
320 | Extend _base_parser method with subclass specific arguments. |
||
321 | |||
322 | :return: parser |
||
323 | :rtype: argparse.ArgumentParser |
||
324 | """ |
||
325 | # create the parser. |
||
326 | parser: argparse.ArgumentParser = argparse.ArgumentParser(add_help=False) |
||
327 | # add command line options. |
||
328 | help_key: str = "key for encryption/decryption" |
||
329 | parser.add_argument("-k", "--key", type=int, required=True, help=help_key) |
||
330 | |||
331 | help_fill: str = "custom fill letter to fill empty cells on the grid" |
||
332 | parser.add_argument("-fi", "--fill", type=str, default="X", help=help_fill) |
||
333 | |||
334 | help_route: str = "route for encryption/decryption" |
||
335 | parser.add_argument( |
||
336 | "-r", "--route", nargs="+", type=int, required=True, help=help_route |
||
337 | ) |
||
338 | |||
339 | return parser |
||
340 |