3333https://www.youtube.com/watch?v=kfmNeskzs2o
3434https://www.youtube.com/watch?v=4RhLNDqcjpA
3535"""
36+
3637import string
3738import numpy as np
3839from maths .greatest_common_divisor import greatest_common_divisor
3940
41+
4042class HillCipher :
4143 key_string = string .ascii_uppercase + string .digits # 36 alphanumeric chars
4244 modulus = np .vectorize (lambda x : x % 36 ) # Mod 36 operation
@@ -60,15 +62,15 @@ def check_determinant(self) -> None:
6062 det = round (np .linalg .det (self .encrypt_key ))
6163 if det < 0 :
6264 det %= len (self .key_string )
63-
65+
6466 error_msg = f"Det { det } not coprime with 36. Try another key."
6567 if greatest_common_divisor (det , len (self .key_string )) != 1 :
6668 raise ValueError (error_msg )
6769
6870 def process_text (self , text : str ) -> str :
6971 """Convert to uppercase, remove invalid chars, pad to multiple of break_key"""
7072 chars = [c for c in text .upper () if c in self .key_string ]
71- last = chars [- 1 ] if chars else 'A'
73+ last = chars [- 1 ] if chars else "A"
7274 while len (chars ) % self .break_key != 0 :
7375 chars .append (last )
7476 return "" .join (chars )
@@ -78,10 +80,12 @@ def encrypt(self, text: str) -> str:
7880 text = self .process_text (text .upper ())
7981 encrypted = ""
8082 for i in range (0 , len (text ), self .break_key ):
81- batch = text [i : i + self .break_key ]
83+ batch = text [i : i + self .break_key ]
8284 vec = [self .replace_letters (c ) for c in batch ]
8385 batch_vec = np .array ([vec ]).T
84- batch_encrypted = self .modulus (self .encrypt_key .dot (batch_vec )).T .tolist ()[0 ]
86+ batch_encrypted = self .modulus (self .encrypt_key .dot (batch_vec )).T .tolist ()[
87+ 0
88+ ]
8589 encrypted += "" .join (self .replace_digits (round (n )) for n in batch_encrypted )
8690 return encrypted
8791
@@ -90,12 +94,14 @@ def make_decrypt_key(self) -> np.ndarray:
9094 det = round (np .linalg .det (self .encrypt_key ))
9195 if det < 0 :
9296 det %= len (self .key_string )
93-
97+
9498 # Find modular inverse of det
9599 det_inv = next (i for i in range (36 ) if (det * i ) % 36 == 1 )
96-
100+
97101 # Calculate inverse key
98- inv_key = det_inv * np .linalg .det (self .encrypt_key ) * np .linalg .inv (self .encrypt_key )
102+ inv_key = (
103+ det_inv * np .linalg .det (self .encrypt_key ) * np .linalg .inv (self .encrypt_key )
104+ )
99105 return self .to_int (self .modulus (inv_key ))
100106
101107 def decrypt (self , text : str ) -> str :
@@ -104,30 +110,33 @@ def decrypt(self, text: str) -> str:
104110 text = self .process_text (text .upper ())
105111 decrypted = ""
106112 for i in range (0 , len (text ), self .break_key ):
107- batch = text [i : i + self .break_key ]
113+ batch = text [i : i + self .break_key ]
108114 vec = [self .replace_letters (c ) for c in batch ]
109115 batch_vec = np .array ([vec ]).T
110116 batch_decrypted = self .modulus (decrypt_key .dot (batch_vec )).T .tolist ()[0 ]
111117 decrypted += "" .join (self .replace_digits (round (n )) for n in batch_decrypted )
112118 return decrypted
113119
120+
114121def main () -> None :
115122 """CLI for Hill Cipher"""
116123 n = int (input ("Enter key order: " ))
117124 print (f"Enter { n } rows of space-separated integers:" )
118125 matrix = [list (map (int , input ().split ())) for _ in range (n )]
119-
126+
120127 hc = HillCipher (np .array (matrix ))
121-
128+
122129 option = input ("1. Encrypt\n 2. Decrypt\n Choose: " )
123130 text = input ("Enter text: " )
124-
131+
125132 if option == "1" :
126133 print ("Encrypted:" , hc .encrypt (text ))
127134 elif option == "2" :
128135 print ("Decrypted:" , hc .decrypt (text ))
129136
137+
130138if __name__ == "__main__" :
131139 import doctest
140+
132141 doctest .testmod ()
133142 main ()
0 commit comments