Passed
Push — master ( 204f4d...dbabd0 )
by Mohammad Mahdi
01:16
created

mersad/classical/route_cipher.py (1 issue)

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
The variable route does not seem to be defined in case "route" in kwargs on line 174 is False. Are you sure this can never be the case?
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