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,14 +62,14 @@ 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 if greatest_common_divisor (det , len (self .key_string )) != 1 :
6567 raise ValueError (f"Det { det } not coprime with 36. Try another key." )
6668
6769 def process_text (self , text : str ) -> str :
6870 """Convert to uppercase, remove invalid chars, pad to multiple of break_key"""
6971 chars = [c for c in text .upper () if c in self .key_string ]
70- last = chars [- 1 ] if chars else 'A'
72+ last = chars [- 1 ] if chars else "A"
7173 while len (chars ) % self .break_key != 0 :
7274 chars .append (last )
7375 return "" .join (chars )
@@ -77,24 +79,30 @@ def encrypt(self, text: str) -> str:
7779 text = self .process_text (text .upper ())
7880 encrypted = ""
7981 for i in range (0 , len (text ), self .break_key ):
80- batch = text [i : i + self .break_key ]
82+ batch = text [i : i + self .break_key ]
8183 vec = [self .replace_letters (c ) for c in batch ]
8284 batch_vec = np .array ([vec ]).T
83- batch_encrypted = self .modulus (self .encrypt_key .dot (batch_vec )).T .tolist ()[0 ]
84- encrypted += "" .join (self .replace_digits (int (round (n ))) for n in batch_encrypted )
85+ batch_encrypted = self .modulus (self .encrypt_key .dot (batch_vec )).T .tolist ()[
86+ 0
87+ ]
88+ encrypted += "" .join (
89+ self .replace_digits (int (round (n ))) for n in batch_encrypted
90+ )
8591 return encrypted
8692
8793 def make_decrypt_key (self ) -> np .ndarray :
8894 """Calculate decryption key matrix"""
8995 det = round (np .linalg .det (self .encrypt_key ))
9096 if det < 0 :
9197 det %= len (self .key_string )
92-
98+
9399 # Find modular inverse of det
94100 det_inv = next (i for i in range (36 ) if (det * i ) % 36 == 1 )
95-
101+
96102 # Calculate inverse key
97- inv_key = det_inv * np .linalg .det (self .encrypt_key ) * np .linalg .inv (self .encrypt_key )
103+ inv_key = (
104+ det_inv * np .linalg .det (self .encrypt_key ) * np .linalg .inv (self .encrypt_key )
105+ )
98106 return self .to_int (self .modulus (inv_key ))
99107
100108 def decrypt (self , text : str ) -> str :
@@ -103,30 +111,35 @@ def decrypt(self, text: str) -> str:
103111 text = self .process_text (text .upper ())
104112 decrypted = ""
105113 for i in range (0 , len (text ), self .break_key ):
106- batch = text [i : i + self .break_key ]
114+ batch = text [i : i + self .break_key ]
107115 vec = [self .replace_letters (c ) for c in batch ]
108116 batch_vec = np .array ([vec ]).T
109117 batch_decrypted = self .modulus (decrypt_key .dot (batch_vec )).T .tolist ()[0 ]
110- decrypted += "" .join (self .replace_digits (int (round (n ))) for n in batch_decrypted )
118+ decrypted += "" .join (
119+ self .replace_digits (int (round (n ))) for n in batch_decrypted
120+ )
111121 return decrypted
112122
123+
113124def main () -> None :
114125 """CLI for Hill Cipher"""
115126 n = int (input ("Enter key order: " ))
116127 print (f"Enter { n } rows of space-separated integers:" )
117128 matrix = [list (map (int , input ().split ())) for _ in range (n )]
118-
129+
119130 hc = HillCipher (np .array (matrix ))
120-
131+
121132 option = input ("1. Encrypt\n 2. Decrypt\n Choose: " )
122133 text = input ("Enter text: " )
123-
134+
124135 if option == "1" :
125136 print ("Encrypted:" , hc .encrypt (text ))
126137 elif option == "2" :
127138 print ("Decrypted:" , hc .decrypt (text ))
128139
140+
129141if __name__ == "__main__" :
130142 import doctest
143+
131144 doctest .testmod ()
132145 main ()
0 commit comments