33import numpy as np
44from maths .greatest_common_divisor import greatest_common_divisor
55
6+
67class HillCipher :
78 key_string = string .ascii_uppercase + string .digits # 36 chars
89 modulus = np .vectorize (lambda x : x % 36 ) # Mod 36
@@ -26,15 +27,15 @@ def check_determinant(self) -> None:
2627 det = round (np .linalg .det (self .encrypt_key ))
2728 if det < 0 :
2829 det %= len (self .key_string )
29-
30+
3031 error_msg = f"Det { det } not coprime with 36. Try another key."
3132 if greatest_common_divisor (det , len (self .key_string )) != 1 :
3233 raise ValueError (error_msg )
3334
3435 def process_text (self , text : str ) -> str :
3536 """Uppercase, filter, pad text"""
3637 chars = [c for c in text .upper () if c in self .key_string ]
37- last = chars [- 1 ] if chars else 'A'
38+ last = chars [- 1 ] if chars else "A"
3839 while len (chars ) % self .break_key != 0 :
3940 chars .append (last )
4041 return "" .join (chars )
@@ -44,7 +45,7 @@ def encrypt(self, text: str) -> str:
4445 text = self .process_text (text .upper ())
4546 encrypted = ""
4647 for i in range (0 , len (text ), self .break_key ):
47- batch = text [i : i + self .break_key ]
48+ batch = text [i : i + self .break_key ]
4849 vec = [self .replace_letters (c ) for c in batch ]
4950 batch_vec = np .array ([vec ]).T
5051 product = self .encrypt_key .dot (batch_vec )
@@ -61,15 +62,13 @@ def make_decrypt_key(self) -> np.ndarray:
6162 det = round (np .linalg .det (self .encrypt_key ))
6263 if det < 0 :
6364 det %= len (self .key_string )
64-
65+
6566 # Find det modular inverse
6667 det_inv = next (i for i in range (36 ) if (det * i ) % 36 == 1 )
67-
68+
6869 # Compute inverse key
6970 inv_key = (
70- det_inv *
71- np .linalg .det (self .encrypt_key ) *
72- np .linalg .inv (self .encrypt_key )
71+ det_inv * np .linalg .det (self .encrypt_key ) * np .linalg .inv (self .encrypt_key )
7372 )
7473 return self .to_int (self .modulus (inv_key ))
7574
@@ -79,7 +78,7 @@ def decrypt(self, text: str) -> str:
7978 text = self .process_text (text .upper ())
8079 decrypted = ""
8180 for i in range (0 , len (text ), self .break_key ):
82- batch = text [i : i + self .break_key ]
81+ batch = text [i : i + self .break_key ]
8382 vec = [self .replace_letters (c ) for c in batch ]
8483 batch_vec = np .array ([vec ]).T
8584 product = decrypt_key .dot (batch_vec )
@@ -91,23 +90,26 @@ def decrypt(self, text: str) -> str:
9190 decrypted += decrypted_batch
9291 return decrypted
9392
93+
9494def main () -> None :
9595 """Hill Cipher CLI"""
9696 n = int (input ("Enter key order: " ))
9797 print (f"Enter { n } rows of space-separated integers:" )
9898 matrix = [list (map (int , input ().split ())) for _ in range (n )]
99-
99+
100100 hc = HillCipher (np .array (matrix ))
101-
101+
102102 option = input ("1. Encrypt\n 2. Decrypt\n Choose: " )
103103 text = input ("Enter text: " )
104-
104+
105105 if option == "1" :
106106 print ("Encrypted:" , hc .encrypt (text ))
107107 elif option == "2" :
108108 print ("Decrypted:" , hc .decrypt (text ))
109109
110+
110111if __name__ == "__main__" :
111112 import doctest
113+
112114 doctest .testmod ()
113115 main ()
0 commit comments