1
1
"""
2
2
3
3
Hill 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.
8
7
9
8
Algorithm:
10
9
Let the order of the encryption key be N (as it is a square matrix).
24
23
The determinant of the encryption key matrix must be relatively prime w.r.t 36.
25
24
26
25
Note:
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.
33
31
34
32
References:
35
33
https://apprendre-en-ligne.net/crypto/hill/Hillciph.pdf
38
36
39
37
"""
40
38
39
+ import string
41
40
import numpy
42
41
43
42
44
- def gcd (a : int , b : int ) -> int :
43
+ def greatest_common_divisor (a : int , b : int ) -> int :
45
44
"""
46
- >>> gcd (4, 8)
45
+ >>> greatest_common_divisor (4, 8)
47
46
4
48
- >>> gcd (8, 4)
47
+ >>> greatest_common_divisor (8, 4)
49
48
4
50
- >>> gcd (4, 7)
49
+ >>> greatest_common_divisor (4, 7)
51
50
1
52
- >>> gcd (0, 10)
51
+ >>> greatest_common_divisor (0, 10)
53
52
10
54
53
"""
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 )
58
55
59
56
60
57
class HillCipher :
61
- key_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
58
+ key_string = string . ascii_uppercase + string . digits
62
59
# This cipher takes alphanumerics into account
63
60
# i.e. a total of 36 characters
64
61
65
62
# take x and return x % len(key_string)
66
63
modulus = numpy .vectorize (lambda x : x % 36 )
67
64
68
- toInt = numpy .vectorize (lambda x : round (x ))
65
+ to_int = numpy .vectorize (lambda x : round (x ))
69
66
70
67
def __init__ (self , encrypt_key ):
71
68
"""
72
- encrypt_key is an NxN numpy matrix
69
+ encrypt_key is an NxN numpy array
73
70
"""
74
71
self .encrypt_key = self .modulus (encrypt_key ) # mod36 calc's on the encrypt key
75
72
self .check_determinant () # validate the determinant of the encryption key
76
73
self .decrypt_key = None
77
74
self .break_key = encrypt_key .shape [0 ]
78
75
79
- def replaceLetters (self , letter : str ) -> int :
76
+ def replace_letters (self , letter : str ) -> int :
80
77
"""
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')
83
80
19
84
- >>> hill_cipher.replaceLetters ('0')
81
+ >>> hill_cipher.replace_letters ('0')
85
82
26
86
83
"""
87
84
return self .key_string .index (letter )
88
85
89
- def replaceNumbers (self , num : int ) -> str :
86
+ def replace_digits (self , num : int ) -> str :
90
87
"""
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)
93
90
'T'
94
- >>> hill_cipher.replaceNumbers (26)
91
+ >>> hill_cipher.replace_digits (26)
95
92
'0'
96
93
"""
97
94
return self .key_string [round (num )]
98
95
99
96
def check_determinant (self ) -> None :
100
97
"""
101
- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
98
+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
102
99
>>> hill_cipher.check_determinant()
103
100
"""
104
101
det = round (numpy .linalg .det (self .encrypt_key ))
@@ -107,19 +104,20 @@ def check_determinant(self) -> None:
107
104
det = det % len (self .key_string )
108
105
109
106
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 :
111
108
raise ValueError (
112
109
f"determinant modular { req_l } of encryption key({ det } ) is not co prime w.r.t { req_l } .\n Try another key."
113
110
)
114
111
115
112
def process_text (self , text : str ) -> str :
116
113
"""
117
- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
114
+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
118
115
>>> hill_cipher.process_text('Testing Hill Cipher')
119
116
'TESTINGHILLCIPHERR'
117
+ >>> hill_cipher.process_text('hello')
118
+ 'HELLOO'
120
119
"""
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 ]
123
121
124
122
last = chars [- 1 ]
125
123
while len (chars ) % self .break_key != 0 :
@@ -129,33 +127,35 @@ def process_text(self, text: str) -> str:
129
127
130
128
def encrypt (self , text : str ) -> str :
131
129
"""
132
- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
130
+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
133
131
>>> hill_cipher.encrypt('testing hill cipher')
134
132
'WHXYJOLM9C6XT085LL'
133
+ >>> hill_cipher.encrypt('hello')
134
+ '85FF00'
135
135
"""
136
136
text = self .process_text (text .upper ())
137
137
encrypted = ""
138
138
139
139
for i in range (0 , len (text ) - self .break_key + 1 , self .break_key ):
140
140
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
143
143
batch_encrypted = self .modulus (self .encrypt_key .dot (batch_vec )).T .tolist ()[
144
144
0
145
145
]
146
146
encrypted_batch = "" .join (
147
- self .replaceNumbers (num ) for num in batch_encrypted
147
+ self .replace_digits (num ) for num in batch_encrypted
148
148
)
149
149
encrypted += encrypted_batch
150
150
151
151
return encrypted
152
152
153
153
def make_decrypt_key (self ):
154
154
"""
155
- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
155
+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
156
156
>>> hill_cipher.make_decrypt_key()
157
- matrix ([[ 6., 25.],
158
- [ 5., 26.]])
157
+ array ([[ 6., 25.],
158
+ [ 5., 26.]])
159
159
"""
160
160
det = round (numpy .linalg .det (self .encrypt_key ))
161
161
@@ -173,27 +173,29 @@ def make_decrypt_key(self):
173
173
* numpy .linalg .inv (self .encrypt_key )
174
174
)
175
175
176
- return self .toInt (self .modulus (inv_key ))
176
+ return self .to_int (self .modulus (inv_key ))
177
177
178
178
def decrypt (self , text : str ) -> str :
179
179
"""
180
- >>> hill_cipher = HillCipher(numpy.matrix ([[2, 5], [1, 6]]))
180
+ >>> hill_cipher = HillCipher(numpy.array ([[2, 5], [1, 6]]))
181
181
>>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL')
182
182
'TESTINGHILLCIPHERR'
183
+ >>> hill_cipher.decrypt('85FF00')
184
+ 'HELLOO'
183
185
"""
184
186
self .decrypt_key = self .make_decrypt_key ()
185
187
text = self .process_text (text .upper ())
186
188
decrypted = ""
187
189
188
190
for i in range (0 , len (text ) - self .break_key + 1 , self .break_key ):
189
191
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
192
194
batch_decrypted = self .modulus (self .decrypt_key .dot (batch_vec )).T .tolist ()[
193
195
0
194
196
]
195
197
decrypted_batch = "" .join (
196
- self .replaceNumbers (num ) for num in batch_decrypted
198
+ self .replace_digits (num ) for num in batch_decrypted
197
199
)
198
200
decrypted += decrypted_batch
199
201
@@ -206,19 +208,13 @@ def main():
206
208
207
209
print ("Enter each row of the encryption key with space separated integers" )
208
210
for i in range (N ):
209
- row = list ( map ( int , input ().split ()))
211
+ row = [ int ( x ) for x in input ().split ()]
210
212
hill_matrix .append (row )
211
213
212
- hc = HillCipher (numpy .matrix (hill_matrix ))
214
+ hc = HillCipher (numpy .array (hill_matrix ))
213
215
214
216
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 " )
222
218
if option == "1" :
223
219
text_e = input ("What text would you like to encrypt?: " )
224
220
print ("Your encrypted text is:" )
@@ -231,6 +227,7 @@ def main():
231
227
232
228
if __name__ == "__main__" :
233
229
import doctest
230
+
234
231
doctest .testmod ()
235
232
236
233
main ()
0 commit comments