@@ -146,7 +146,7 @@ def check_determinant(self) -> None:
146
146
# Optimized determinant calculation to avoid redundant rounding
147
147
det_value = np .linalg .det (self .encrypt_key )
148
148
det = int (round (det_value )) if not det_value .is_integer () else int (det_value )
149
-
149
+
150
150
if det < 0 :
151
151
det = det % len (self .key_string )
152
152
@@ -157,6 +157,7 @@ def check_determinant(self) -> None:
157
157
f"w.r.t { req_l } .\n Try another key."
158
158
)
159
159
raise ValueError (msg )
160
+
160
161
def process_text (self , text : str ) -> str :
161
162
"""
162
163
Prepare text for encryption/decryption by:
@@ -183,16 +184,17 @@ def process_text(self, text: str) -> str:
183
184
'ABCC'
184
185
"""
185
186
chars = [char for char in text .upper () if char in self .key_string ]
186
-
187
+
187
188
# Handle empty input case
188
189
if not chars :
189
190
return ""
190
-
191
+
191
192
last = chars [- 1 ]
192
193
while len (chars ) % self .break_key != 0 :
193
194
chars .append (last )
194
-
195
+
195
196
return "" .join (chars )
197
+
196
198
def encrypt (self , text : str ) -> str :
197
199
"""
198
200
Encrypt plaintext using Hill Cipher.
@@ -220,21 +222,21 @@ def encrypt(self, text: str) -> str:
220
222
text = self .process_text (text .upper ())
221
223
if not text :
222
224
return ""
223
-
225
+
224
226
encrypted = ""
225
227
226
228
for i in range (0 , len (text ) - self .break_key + 1 , self .break_key ):
227
229
# Extract batch of characters
228
230
batch = text [i : i + self .break_key ]
229
-
231
+
230
232
# Convert to numerical vector
231
233
vec = [self .replace_letters (char ) for char in batch ]
232
234
batch_vec = np .array ([vec ]).T
233
-
235
+
234
236
# Matrix multiplication and mod 36
235
237
product = self .encrypt_key .dot (batch_vec )
236
238
batch_encrypted = self .modulus (product ).T .tolist ()[0 ]
237
-
239
+
238
240
# Convert back to characters
239
241
encrypted_batch = "" .join (
240
242
self .replace_digits (num ) for num in batch_encrypted
@@ -259,7 +261,7 @@ def make_decrypt_key(self) -> np.ndarray:
259
261
>>> cipher.make_decrypt_key()
260
262
array([[ 6, 25],
261
263
[ 5, 26]])
262
-
264
+
263
265
>>> key3x3 = np.array([[1,2,3],[4,5,6],[7,8,9]])
264
266
>>> cipher3 = HillCipher(key3x3)
265
267
>>> cipher3.make_decrypt_key() # Determinant 0 should be invalid
@@ -271,10 +273,10 @@ def make_decrypt_key(self) -> np.ndarray:
271
273
# Optimized determinant calculation to avoid redundant rounding
272
274
det_value = np .linalg .det (self .encrypt_key )
273
275
det = int (round (det_value )) if not det_value .is_integer () else int (det_value )
274
-
276
+
275
277
if det < 0 :
276
278
det = det % len (self .key_string )
277
-
279
+
278
280
det_inv : int | None = None
279
281
for i in range (len (self .key_string )):
280
282
if (det * i ) % len (self .key_string ) == 1 :
@@ -317,22 +319,22 @@ def decrypt(self, text: str) -> str:
317
319
text = self .process_text (text .upper ())
318
320
if not text :
319
321
return ""
320
-
322
+
321
323
decrypt_key = self .make_decrypt_key ()
322
324
decrypted = ""
323
325
324
326
for i in range (0 , len (text ) - self .break_key + 1 , self .break_key ):
325
327
# Extract batch of characters
326
328
batch = text [i : i + self .break_key ]
327
-
329
+
328
330
# Convert to numerical vector
329
331
vec = [self .replace_letters (char ) for char in batch ]
330
332
batch_vec = np .array ([vec ]).T
331
-
333
+
332
334
# Matrix multiplication and mod 36
333
335
product = decrypt_key .dot (batch_vec )
334
336
batch_decrypted = self .modulus (product ).T .tolist ()[0 ]
335
-
337
+
336
338
# Convert back to characters
337
339
decrypted_batch = "" .join (
338
340
self .replace_digits (num ) for num in batch_decrypted
@@ -342,11 +344,10 @@ def decrypt(self, text: str) -> str:
342
344
return decrypted
343
345
344
346
345
-
346
347
def main () -> None :
347
348
"""
348
349
Command-line interface for Hill Cipher operations.
349
-
350
+
350
351
Steps:
351
352
1. User inputs encryption key size
352
353
2. User inputs encryption key matrix rows
@@ -359,14 +360,14 @@ def main() -> None:
359
360
360
361
print ("Enter each row of the encryption key with space separated integers" )
361
362
for i in range (n ):
362
- row = [int (x ) for x in input (f"Row { i + 1 } : " ).split ()]
363
+ row = [int (x ) for x in input (f"Row { i + 1 } : " ).split ()]
363
364
hill_matrix .append (row )
364
365
365
366
hc = HillCipher (np .array (hill_matrix ))
366
367
367
368
print ("\n Would you like to encrypt or decrypt some text?" )
368
369
option = input ("1. Encrypt\n 2. Decrypt\n Enter choice (1/2): " )
369
-
370
+
370
371
if option == "1" :
371
372
text = input ("\n Enter text to encrypt: " )
372
373
print ("\n Encrypted text:" )
@@ -378,22 +379,24 @@ def main() -> None:
378
379
else :
379
380
print ("Invalid option selected" )
380
381
382
+
381
383
if __name__ == "__main__" :
382
384
import doctest
385
+
383
386
doctest .testmod ()
384
-
387
+
385
388
print ("\n Running sample tests..." )
386
389
key = np .array ([[2 , 5 ], [1 , 6 ]])
387
390
cipher = HillCipher (key )
388
-
391
+
389
392
# Test encryption/decryption round trip
390
393
plaintext = "HELLO123"
391
394
encrypted = cipher .encrypt (plaintext )
392
395
decrypted = cipher .decrypt (encrypted )
393
-
396
+
394
397
print (f"\n Original text: { plaintext } " )
395
398
print (f"Encrypted text: { encrypted } " )
396
399
print (f"Decrypted text: { decrypted } " )
397
-
400
+
398
401
# Run CLI interface
399
402
main ()
0 commit comments