Skip to content

Commit 2b27086

Browse files
JekyllAndHyde8999poyea
authored andcommitted
Added hill_cipher.py (#696)
A python class that implements the Hill cipher algorithm.
1 parent 74e94ab commit 2b27086

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed

ciphers/hill_cipher.py

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

0 commit comments

Comments
 (0)