1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ reoute-cipher.py
5
+ """
6
+
7
+ from pathlib import Path
8
+ from copy import deepcopy
9
+ from argparse import ArgumentParser
10
+ import logging
11
+ import sys
12
+
13
+ # TEST_CIPHERTEXT = '16 12 8 4 0 1 5 9 13 17 18 14 10 6 2 3 7 11 15 19'
14
+ # TEST_COLS = 4
15
+ # TEST_ROWS = 5
16
+ # TEST_KEY = '-1 2 -3 4' # negative reads upwards, positive downwards
17
+
18
+ logger = None
19
+
20
+ def main ():
21
+ args = parse_arguments ()
22
+ logger = init_logger (args .verbose )
23
+
24
+ if args .file :
25
+ logger .info (f'Reading from file at { args .file .resolve ()} ' )
26
+ with open (args .file ) as f :
27
+ ciphertext = f .read ()
28
+ else :
29
+ ciphertext = args .message
30
+
31
+ validate_length (ciphertext , args .rows , args .columns )
32
+
33
+ translation_matrix = create_matrix (
34
+ ciphertext ,
35
+ args .key ,
36
+ args .columns ,
37
+ args .rows
38
+ )
39
+
40
+ print (logger .getEffectiveLevel ())
41
+
42
+ if logger .getEffectiveLevel () >= 10 :
43
+ print_matrix (translation_matrix )
44
+
45
+ plaintext = decrypt (translation_matrix , args .rows )
46
+
47
+ print (plaintext )
48
+
49
+
50
+ def validate_length (ciphertext , rows , cols ):
51
+ """
52
+ Checks that the input columns and rows are valid respective to the
53
+ ciphertext length.
54
+ """
55
+ factors = []
56
+ len_cipher = len (ciphertext .split ())
57
+
58
+ # Exclude 1x1 grids as that would be ciphertext
59
+ for i in range (2 , len_cipher ):
60
+ if len_cipher % i == 0 :
61
+ factors .append (i )
62
+
63
+ if rows * cols != len_cipher :
64
+ print ("Error - Input columns and rows not factors of length of ciphertext." )
65
+ sys .exit (1 )
66
+
67
+
68
+ def validate_key (key , cols ):
69
+ """Turn key into list of integers and checks it for validity"""
70
+
71
+ key_int = [int (k ) for k in key .split ()]
72
+ lowest = min (key_int )
73
+ highest = max (key_int )
74
+
75
+ if 0 in key_int :
76
+ print ('Invalid key - 0 is not a valid value' )
77
+ sys .exit (1 )
78
+
79
+ if (
80
+ len (key_int ) != cols or \
81
+ lowest < - cols or \
82
+ highest > cols
83
+ ):
84
+ print ('Invalid key - Invalid number of of columns and/or rows' )
85
+
86
+ return key_int
87
+
88
+
89
+ def decrypt (matrix , rows ):
90
+ plaintext = ''
91
+ matrix_copy = deepcopy (matrix )
92
+
93
+ for _ in range (rows ):
94
+ for col in matrix_copy :
95
+ word = str (col .pop ())
96
+ plaintext += word + ' '
97
+
98
+ return plaintext
99
+
100
+
101
+ def create_matrix (ciphertext , key , cols , rows ):
102
+ """Turns a given ciphertext into a matrix of size cols x rows"""
103
+
104
+ ciphertext_list = ciphertext .split ()
105
+ translation_matrix = [None ] * cols
106
+ start = 0
107
+ stop = rows
108
+
109
+ for k in validate_key (key , cols ):
110
+
111
+ col_items = ciphertext_list [start :stop ]
112
+
113
+ # Reverse order of ciphertext based on sign on key
114
+ if int (k ) > 0 :
115
+ col_items = list (reversed (col_items ))
116
+
117
+ # Subtract one for python's 0 index
118
+ translation_matrix [abs (int (k )) - 1 ] = col_items
119
+
120
+ # Increase counters for next iteration
121
+ start += rows
122
+ stop += rows
123
+
124
+ return translation_matrix
125
+
126
+
127
+ def print_matrix (matrix ):
128
+ print ('Translation Matrix' , * matrix , sep = '\n ' )
129
+
130
+
131
+ def init_logger (verbosity ):
132
+ logger = logging .getLogger (__name__ )
133
+ if verbosity == 1 :
134
+ logger .setLevel (logging .INFO )
135
+ if verbosity > 1 :
136
+ logger .setLevel (logging .DEBUG )
137
+ return logger
138
+
139
+
140
+ def parse_arguments ():
141
+ parser = ArgumentParser ()
142
+
143
+ parser .add_argument ('-k' , '--key' ,
144
+ help = 'key used to encrypt/decrypt the message'
145
+ )
146
+ parser .add_argument ('-c' , '--columns' ,
147
+ help = 'Number of "columns" to use to create the route cipher' ,
148
+ type = int
149
+ )
150
+ parser .add_argument ('-r' , '--rows' ,
151
+ help = 'Number of "rows" to use to create the route cipher' ,
152
+ type = int
153
+ )
154
+
155
+ operations = parser .add_mutually_exclusive_group (required = True )
156
+ operations .add_argument ('-e' , '--encrypt' ,
157
+ help = 'run script with encryption instructions' ,
158
+ action = 'store_true'
159
+ )
160
+ operations .add_argument ('-d' , '--decrypt' ,
161
+ help = 'run script with decryption instructions' ,
162
+ action = 'store_true'
163
+ )
164
+
165
+ source = parser .add_mutually_exclusive_group (required = True )
166
+ source .add_argument ('-m' , '--message' ,
167
+ help = 'string to either encrypt or decrypt'
168
+ )
169
+ source .add_argument ('-f' , '--file' ,
170
+ help = 'path to file containing the ciphertext' ,
171
+ type = Path
172
+ )
173
+
174
+ parser .add_argument ('-v' , '--verbose' ,
175
+ help = 'prints additional information to terminal output' ,
176
+ action = 'count'
177
+ )
178
+ return parser .parse_args ()
179
+
180
+
181
+ if __name__ == '__main__' :
182
+ main ()
0 commit comments