18
18
https://www.youtube.com/watch?v=4RhLNDqcjpA
19
19
"""
20
20
21
+ # Standard library imports
21
22
import string
22
23
24
+ # Third-party imports
23
25
import numpy as np
26
+
27
+ # Local application imports
24
28
from maths .greatest_common_divisor import greatest_common_divisor
25
29
26
30
@@ -117,6 +121,7 @@ def replace_digits(self, num: int) -> str:
117
121
"""
118
122
return self .key_string [round (num )]
119
123
124
+
120
125
def check_determinant (self ) -> None :
121
126
"""
122
127
Validate encryption key determinant.
@@ -144,10 +149,10 @@ def check_determinant(self) -> None:
144
149
ValueError: determinant modular 36 of encryption key(0) is not co prime
145
150
w.r.t 36. Try another key.
146
151
"""
147
- # Optimized determinant calculation to avoid redundant rounding
148
152
det_value = np .linalg .det (self .encrypt_key )
149
- det = int (round (det_value )) if not det_value .is_integer () else int (det_value )
150
-
153
+ # Only round if necessary
154
+ det = int (det_value ) if det_value .is_integer () else int (round (det_value ))
155
+
151
156
if det < 0 :
152
157
det = det % len (self .key_string )
153
158
@@ -159,6 +164,7 @@ def check_determinant(self) -> None:
159
164
)
160
165
raise ValueError (msg )
161
166
167
+
162
168
def process_text (self , text : str ) -> str :
163
169
"""
164
170
Prepare text for encryption/decryption by:
@@ -185,15 +191,15 @@ def process_text(self, text: str) -> str:
185
191
'ABCC'
186
192
"""
187
193
chars = [char for char in text .upper () if char in self .key_string ]
188
-
194
+
189
195
# Handle empty input case
190
196
if not chars :
191
197
return ""
192
-
198
+
193
199
last = chars [- 1 ]
194
200
while len (chars ) % self .break_key != 0 :
195
201
chars .append (last )
196
-
202
+
197
203
return "" .join (chars )
198
204
199
205
def encrypt (self , text : str ) -> str :
@@ -223,21 +229,21 @@ def encrypt(self, text: str) -> str:
223
229
text = self .process_text (text .upper ())
224
230
if not text :
225
231
return ""
226
-
232
+
227
233
encrypted = ""
228
234
229
235
for i in range (0 , len (text ) - self .break_key + 1 , self .break_key ):
230
236
# Extract batch of characters
231
237
batch = text [i : i + self .break_key ]
232
-
238
+
233
239
# Convert to numerical vector
234
240
vec = [self .replace_letters (char ) for char in batch ]
235
241
batch_vec = np .array ([vec ]).T
236
-
242
+
237
243
# Matrix multiplication and mod 36
238
244
product = self .encrypt_key .dot (batch_vec )
239
245
batch_encrypted = self .modulus (product ).T .tolist ()[0 ]
240
-
246
+
241
247
# Convert back to characters
242
248
encrypted_batch = "" .join (
243
249
self .replace_digits (num ) for num in batch_encrypted
@@ -262,7 +268,7 @@ def make_decrypt_key(self) -> np.ndarray:
262
268
>>> cipher.make_decrypt_key()
263
269
array([[ 6, 25],
264
270
[ 5, 26]])
265
-
271
+
266
272
>>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]])
267
273
>>> cipher3 = HillCipher(key3x3)
268
274
>>> cipher3.make_decrypt_key() # Determinant 0 should be invalid
@@ -271,13 +277,13 @@ def make_decrypt_key(self) -> np.ndarray:
271
277
ValueError: determinant modular 36 of encryption key(0) is not co prime
272
278
w.r.t 36. Try another key.
273
279
"""
274
- # Optimized determinant calculation to avoid redundant rounding
275
280
det_value = np .linalg .det (self .encrypt_key )
276
- det = int (round (det_value )) if not det_value .is_integer () else int (det_value )
277
-
281
+ # Only round if necessary
282
+ det = int (det_value ) if det_value .is_integer () else int (round (det_value ))
283
+
278
284
if det < 0 :
279
285
det = det % len (self .key_string )
280
-
286
+
281
287
det_inv : int | None = None
282
288
for i in range (len (self .key_string )):
283
289
if (det * i ) % len (self .key_string ) == 1 :
@@ -320,22 +326,22 @@ def decrypt(self, text: str) -> str:
320
326
text = self .process_text (text .upper ())
321
327
if not text :
322
328
return ""
323
-
329
+
324
330
decrypt_key = self .make_decrypt_key ()
325
331
decrypted = ""
326
332
327
333
for i in range (0 , len (text ) - self .break_key + 1 , self .break_key ):
328
334
# Extract batch of characters
329
335
batch = text [i : i + self .break_key ]
330
-
336
+
331
337
# Convert to numerical vector
332
338
vec = [self .replace_letters (char ) for char in batch ]
333
339
batch_vec = np .array ([vec ]).T
334
-
340
+
335
341
# Matrix multiplication and mod 36
336
342
product = decrypt_key .dot (batch_vec )
337
343
batch_decrypted = self .modulus (product ).T .tolist ()[0 ]
338
-
344
+
339
345
# Convert back to characters
340
346
decrypted_batch = "" .join (
341
347
self .replace_digits (num ) for num in batch_decrypted
@@ -348,7 +354,7 @@ def decrypt(self, text: str) -> str:
348
354
def main () -> None :
349
355
"""
350
356
Command-line interface for Hill Cipher operations.
351
-
357
+
352
358
Steps:
353
359
1. User inputs encryption key size
354
360
2. User inputs encryption key matrix rows
@@ -361,14 +367,14 @@ def main() -> None:
361
367
362
368
print ("Enter each row of the encryption key with space separated integers" )
363
369
for i in range (n ):
364
- row = [int (x ) for x in input (f"Row { i + 1 } : " ).split ()]
370
+ row = [int (x ) for x in input (f"Row { i + 1 } : " ).split ()]
365
371
hill_matrix .append (row )
366
372
367
373
hc = HillCipher (np .array (hill_matrix ))
368
374
369
375
print ("\n Would you like to encrypt or decrypt some text?" )
370
376
option = input ("1. Encrypt\n 2. Decrypt\n Enter choice (1/2): " )
371
-
377
+
372
378
if option == "1" :
373
379
text = input ("\n Enter text to encrypt: " )
374
380
print ("\n Encrypted text:" )
@@ -383,21 +389,20 @@ def main() -> None:
383
389
384
390
if __name__ == "__main__" :
385
391
import doctest
386
-
387
392
doctest .testmod ()
388
-
393
+
389
394
print ("\n Running sample tests..." )
390
395
key = np .array ([[2 , 5 ], [1 , 6 ]])
391
396
cipher = HillCipher (key )
392
-
397
+
393
398
# Test encryption/decryption round trip
394
399
plaintext = "HELLO123"
395
400
encrypted = cipher .encrypt (plaintext )
396
401
decrypted = cipher .decrypt (encrypted )
397
-
402
+
398
403
print (f"\n Original text: { plaintext } " )
399
404
print (f"Encrypted text: { encrypted } " )
400
405
print (f"Decrypted text: { decrypted } " )
401
-
406
+
402
407
# Run CLI interface
403
408
main ()
0 commit comments