11"""
22
33Hill Cipher:
4- The below defined class 'HillCipher' implements the Hill Cipher algorithm.
5- The Hill Cipher is an algorithm that implements modern linear algebra techniques
6- In this algorithm, you have an encryption key matrix. This is what will be used
7- in encoding and decoding your text.
4+ The 'HillCipher' class below implements the Hill Cipher algorithm which uses
5+ modern linear algebra techniques to encode and decode text using an encryption
6+ key matrix.
87
98Algorithm:
109Let the order of the encryption key be N (as it is a square matrix).
2423The determinant of the encryption key matrix must be relatively prime w.r.t 36.
2524
2625Note:
27- The algorithm implemented in this code considers only alphanumerics in the text.
28- If the length of the text to be encrypted is not a multiple of the
29- break key(the length of one batch of letters),the last character of the text
30- is added to the text until the length of the text reaches a multiple of
31- the break_key. So the text after decrypting might be a little different than
32- the original text.
26+ This implementation only considers alphanumerics in the text. If the length of
27+ the text to be encrypted is not a multiple of the break key(the length of one
28+ batch of letters), the last character of the text is added to the text until the
29+ length of the text reaches a multiple of the break_key. So the text after
30+ decrypting might be a little different than the original text.
3331
3432References:
3533https://apprendre-en-ligne.net/crypto/hill/Hillciph.pdf
3836
3937"""
4038
39+ import string
4140import numpy
4241
4342
44- def gcd (a : int , b : int ) -> int :
43+ def greatest_common_divisor (a : int , b : int ) -> int :
4544 """
46- >>> gcd (4, 8)
45+ >>> greatest_common_divisor (4, 8)
4746 4
48- >>> gcd (8, 4)
47+ >>> greatest_common_divisor (8, 4)
4948 4
50- >>> gcd (4, 7)
49+ >>> greatest_common_divisor (4, 7)
5150 1
52- >>> gcd (0, 10)
51+ >>> greatest_common_divisor (0, 10)
5352 10
5453 """
55- if a == 0 :
56- return b
57- return gcd (b % a , a )
54+ return b if a == 0 else greatest_common_divisor (b % a , a )
5855
5956
6057class HillCipher :
61- key_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
58+ key_string = string . ascii_uppercase + string . digits
6259 # This cipher takes alphanumerics into account
6360 # i.e. a total of 36 characters
6461
6562 # take x and return x % len(key_string)
6663 modulus = numpy .vectorize (lambda x : x % 36 )
6764
68- toInt = numpy .vectorize (lambda x : round (x ))
65+ to_int = numpy .vectorize (lambda x : round (x ))
6966
7067 def __init__ (self , encrypt_key ):
7168 """
72- encrypt_key is an NxN numpy matrix
69+ encrypt_key is an NxN numpy array
7370 """
7471 self .encrypt_key = self .modulus (encrypt_key ) # mod36 calc's on the encrypt key
7572 self .check_determinant () # validate the determinant of the encryption key
7673 self .decrypt_key = None
7774 self .break_key = encrypt_key .shape [0 ]
7875
79- def replaceLetters (self , letter : str ) -> int :
76+ def replace_letters (self , letter : str ) -> int :
8077 """
81- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
82- >>> hill_cipher.replaceLetters ('T')
78+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
79+ >>> hill_cipher.replace_letters ('T')
8380 19
84- >>> hill_cipher.replaceLetters ('0')
81+ >>> hill_cipher.replace_letters ('0')
8582 26
8683 """
8784 return self .key_string .index (letter )
8885
89- def replaceNumbers (self , num : int ) -> str :
86+ def replace_digits (self , num : int ) -> str :
9087 """
91- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
92- >>> hill_cipher.replaceNumbers (19)
88+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
89+ >>> hill_cipher.replace_digits (19)
9390 'T'
94- >>> hill_cipher.replaceNumbers (26)
91+ >>> hill_cipher.replace_digits (26)
9592 '0'
9693 """
9794 return self .key_string [round (num )]
9895
9996 def check_determinant (self ) -> None :
10097 """
101- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
98+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
10299 >>> hill_cipher.check_determinant()
103100 """
104101 det = round (numpy .linalg .det (self .encrypt_key ))
@@ -107,19 +104,20 @@ def check_determinant(self) -> None:
107104 det = det % len (self .key_string )
108105
109106 req_l = len (self .key_string )
110- if gcd (det , len (self .key_string )) != 1 :
107+ if greatest_common_divisor (det , len (self .key_string )) != 1 :
111108 raise ValueError (
112109 f"determinant modular { req_l } of encryption key({ det } ) is not co prime w.r.t { req_l } .\n Try another key."
113110 )
114111
115112 def process_text (self , text : str ) -> str :
116113 """
117- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
114+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
118115 >>> hill_cipher.process_text('Testing Hill Cipher')
119116 'TESTINGHILLCIPHERR'
117+ >>> hill_cipher.process_text('hello')
118+ 'HELLOO'
120119 """
121- text = list (text .upper ())
122- chars = [char for char in text if char in self .key_string ]
120+ chars = [char for char in text .upper () if char in self .key_string ]
123121
124122 last = chars [- 1 ]
125123 while len (chars ) % self .break_key != 0 :
@@ -129,33 +127,35 @@ def process_text(self, text: str) -> str:
129127
130128 def encrypt (self , text : str ) -> str :
131129 """
132- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
130+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
133131 >>> hill_cipher.encrypt('testing hill cipher')
134132 'WHXYJOLM9C6XT085LL'
133+ >>> hill_cipher.encrypt('hello')
134+ '85FF00'
135135 """
136136 text = self .process_text (text .upper ())
137137 encrypted = ""
138138
139139 for i in range (0 , len (text ) - self .break_key + 1 , self .break_key ):
140140 batch = text [i : i + self .break_key ]
141- batch_vec = [self .replaceLetters (char ) for char in batch ]
142- batch_vec = numpy .matrix ([batch_vec ]).T
141+ batch_vec = [self .replace_letters (char ) for char in batch ]
142+ batch_vec = numpy .array ([batch_vec ]).T
143143 batch_encrypted = self .modulus (self .encrypt_key .dot (batch_vec )).T .tolist ()[
144144 0
145145 ]
146146 encrypted_batch = "" .join (
147- self .replaceNumbers (num ) for num in batch_encrypted
147+ self .replace_digits (num ) for num in batch_encrypted
148148 )
149149 encrypted += encrypted_batch
150150
151151 return encrypted
152152
153153 def make_decrypt_key (self ):
154154 """
155- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
155+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
156156 >>> hill_cipher.make_decrypt_key()
157- matrix ([[ 6., 25.],
158- [ 5., 26.]])
157+ array ([[ 6., 25.],
158+ [ 5., 26.]])
159159 """
160160 det = round (numpy .linalg .det (self .encrypt_key ))
161161
@@ -173,27 +173,29 @@ def make_decrypt_key(self):
173173 * numpy .linalg .inv (self .encrypt_key )
174174 )
175175
176- return self .toInt (self .modulus (inv_key ))
176+ return self .to_int (self .modulus (inv_key ))
177177
178178 def decrypt (self , text : str ) -> str :
179179 """
180- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
180+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
181181 >>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL')
182182 'TESTINGHILLCIPHERR'
183+ >>> hill_cipher.decrypt('85FF00')
184+ 'HELLOO'
183185 """
184186 self .decrypt_key = self .make_decrypt_key ()
185187 text = self .process_text (text .upper ())
186188 decrypted = ""
187189
188190 for i in range (0 , len (text ) - self .break_key + 1 , self .break_key ):
189191 batch = text [i : i + self .break_key ]
190- batch_vec = [self .replaceLetters (char ) for char in batch ]
191- batch_vec = numpy .matrix ([batch_vec ]).T
192+ batch_vec = [self .replace_letters (char ) for char in batch ]
193+ batch_vec = numpy .array ([batch_vec ]).T
192194 batch_decrypted = self .modulus (self .decrypt_key .dot (batch_vec )).T .tolist ()[
193195 0
194196 ]
195197 decrypted_batch = "" .join (
196- self .replaceNumbers (num ) for num in batch_decrypted
198+ self .replace_digits (num ) for num in batch_decrypted
197199 )
198200 decrypted += decrypted_batch
199201
@@ -206,19 +208,13 @@ def main():
206208
207209 print ("Enter each row of the encryption key with space separated integers" )
208210 for i in range (N ):
209- row = list ( map ( int , input ().split ()))
211+ row = [ int ( x ) for x in input ().split ()]
210212 hill_matrix .append (row )
211213
212- hc = HillCipher (numpy .matrix (hill_matrix ))
214+ hc = HillCipher (numpy .array (hill_matrix ))
213215
214216 print ("Would you like to encrypt or decrypt some text? (1 or 2)" )
215- option = input (
216- """
217- 1. Encrypt
218- 2. Decrypt
219- """
220- )
221-
217+ option = input ("\n 1. Encrypt\n 2. Decrypt\n " )
222218 if option == "1" :
223219 text_e = input ("What text would you like to encrypt?: " )
224220 print ("Your encrypted text is:" )
@@ -231,6 +227,7 @@ def main():
231227
232228if __name__ == "__main__" :
233229 import doctest
230+
234231 doctest .testmod ()
235232
236233 main ()
0 commit comments