forked from TheAlgorithms/Python
-
Notifications
You must be signed in to change notification settings - Fork 0
/
hill_cipher.py
167 lines (126 loc) · 5.58 KB
/
hill_cipher.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
"""
Hill Cipher:
The below defined class 'HillCipher' implements the Hill Cipher algorithm.
The Hill Cipher is an algorithm that implements modern linear algebra techniques
In this algortihm, you have an encryption key matrix. This is what will be used
in encoding and decoding your text.
Algortihm:
Let the order of the encryption key be N (as it is a square matrix).
Your text is divided into batches of length N and converted to numerical vectors
by a simple mapping starting with A=0 and so on.
The key is then mulitplied with the newly created batch vector to obtain the
encoded vector. After each multiplication modular 36 calculations are performed
on the vectors so as to bring the numbers between 0 and 36 and then mapped with
their corresponding alphanumerics.
While decrypting, the decrypting key is found which is the inverse of the
encrypting key modular 36. The same process is repeated for decrypting to get
the original message back.
Constraints:
The determinant of the encryption key matrix must be relatively prime w.r.t 36.
Note:
The algorithm implemented in this code considers only alphanumerics in the text.
If the length of the text to be encrypted is not a multiple of the
break key(the length of one batch of letters),the last character of the text
is added to the text until the length of the text reaches a multiple of
the break_key. So the text after decrypting might be a little different than
the original text.
References:
https://apprendre-en-ligne.net/crypto/hill/Hillciph.pdf
https://www.youtube.com/watch?v=kfmNeskzs2o
https://www.youtube.com/watch?v=4RhLNDqcjpA
"""
import numpy
def gcd(a, b):
if a == 0:
return b
return gcd(b%a, a)
class HillCipher:
key_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
# This cipher takes alphanumerics into account
# i.e. a total of 36 characters
replaceLetters = lambda self, letter: self.key_string.index(letter)
replaceNumbers = lambda self, num: self.key_string[round(num)]
# take x and return x % len(key_string)
modulus = numpy.vectorize(lambda x: x % 36)
toInt = numpy.vectorize(lambda x: round(x))
def __init__(self, encrypt_key):
"""
encrypt_key is an NxN numpy matrix
"""
self.encrypt_key = self.modulus(encrypt_key) # mod36 calc's on the encrypt key
self.checkDeterminant() # validate the determinant of the encryption key
self.decrypt_key = None
self.break_key = encrypt_key.shape[0]
def checkDeterminant(self):
det = round(numpy.linalg.det(self.encrypt_key))
if det < 0:
det = det % len(self.key_string)
req_l = len(self.key_string)
if gcd(det, len(self.key_string)) != 1:
raise ValueError("discriminant modular {0} of encryption key({1}) is not co prime w.r.t {2}.\nTry another key.".format(req_l, det, req_l))
def processText(self, text):
text = list(text.upper())
text = [char for char in text if char in self.key_string]
last = text[-1]
while len(text) % self.break_key != 0:
text.append(last)
return ''.join(text)
def encrypt(self, text):
text = self.processText(text.upper())
encrypted = ''
for i in range(0, len(text) - self.break_key + 1, self.break_key):
batch = text[i:i+self.break_key]
batch_vec = list(map(self.replaceLetters, batch))
batch_vec = numpy.matrix([batch_vec]).T
batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[0]
encrypted_batch = ''.join(list(map(self.replaceNumbers, batch_encrypted)))
encrypted += encrypted_batch
return encrypted
def makeDecryptKey(self):
det = round(numpy.linalg.det(self.encrypt_key))
if det < 0:
det = det % len(self.key_string)
det_inv = None
for i in range(len(self.key_string)):
if (det * i) % len(self.key_string) == 1:
det_inv = i
break
inv_key = det_inv * numpy.linalg.det(self.encrypt_key) *\
numpy.linalg.inv(self.encrypt_key)
return self.toInt(self.modulus(inv_key))
def decrypt(self, text):
self.decrypt_key = self.makeDecryptKey()
text = self.processText(text.upper())
decrypted = ''
for i in range(0, len(text) - self.break_key + 1, self.break_key):
batch = text[i:i+self.break_key]
batch_vec = list(map(self.replaceLetters, batch))
batch_vec = numpy.matrix([batch_vec]).T
batch_decrypted = self.modulus(self.decrypt_key.dot(batch_vec)).T.tolist()[0]
decrypted_batch = ''.join(list(map(self.replaceNumbers, batch_decrypted)))
decrypted += decrypted_batch
return decrypted
def main():
N = int(input("Enter the order of the encryption key: "))
hill_matrix = []
print("Enter each row of the encryption key with space separated integers")
for i in range(N):
row = list(map(int, input().split()))
hill_matrix.append(row)
hc = HillCipher(numpy.matrix(hill_matrix))
print("Would you like to encrypt or decrypt some text? (1 or 2)")
option = input("""
1. Encrypt
2. Decrypt
"""
)
if option == '1':
text_e = input("What text would you like to encrypt?: ")
print("Your encrypted text is:")
print(hc.encrypt(text_e))
elif option == '2':
text_d = input("What text would you like to decrypt?: ")
print("Your decrypted text is:")
print(hc.decrypt(text_d))
if __name__ == "__main__":
main()