Last active
October 9, 2022 09:09
-
-
Save EppuHeilimo/0a901056f9e48a451e0c30a55537ad1b to your computer and use it in GitHub Desktop.
Hill cipher in python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
def encrypt(msg): | |
# Replace spaces with nothing | |
msg = msg.replace(" ", "") | |
# Ask for keyword and get encryption matrix | |
C = make_key() | |
# Append zero if the messsage isn't divisble by 2 | |
len_check = len(msg) % 2 == 0 | |
if not len_check: | |
msg += "0" | |
# Populate message matrix | |
P = create_matrix_of_integers_from_string(msg) | |
# Calculate length of the message | |
msg_len = int(len(msg) / 2) | |
# Calculate P * C | |
encrypted_msg = "" | |
for i in range(msg_len): | |
# Dot product | |
row_0 = P[0][i] * C[0][0] + P[1][i] * C[0][1] | |
# Modulate and add 65 to get back to the A-Z range in ascii | |
integer = int(row_0 % 26 + 65) | |
# Change back to chr type and add to text | |
encrypted_msg += chr(integer) | |
# Repeat for the second column | |
row_1 = P[0][i] * C[1][0] + P[1][i] * C[1][1] | |
integer = int(row_1 % 26 + 65) | |
encrypted_msg += chr(integer) | |
return encrypted_msg | |
def decrypt(encrypted_msg): | |
# Ask for keyword and get encryption matrix | |
C = make_key() | |
# Inverse matrix | |
determinant = C[0][0] * C[1][1] - C[0][1] * C[1][0] | |
determinant = determinant % 26 | |
multiplicative_inverse = find_multiplicative_inverse(determinant) | |
C_inverse = C | |
# Swap a <-> d | |
C_inverse[0][0], C_inverse[1][1] = C_inverse[1, 1], C_inverse[0, 0] | |
# Replace | |
C[0][1] *= -1 | |
C[1][0] *= -1 | |
for row in range(2): | |
for column in range(2): | |
C_inverse[row][column] *= multiplicative_inverse | |
C_inverse[row][column] = C_inverse[row][column] % 26 | |
P = create_matrix_of_integers_from_string(encrypted_msg) | |
msg_len = int(len(encrypted_msg) / 2) | |
decrypted_msg = "" | |
for i in range(msg_len): | |
# Dot product | |
column_0 = P[0][i] * C_inverse[0][0] + P[1][i] * C_inverse[0][1] | |
# Modulate and add 65 to get back to the A-Z range in ascii | |
integer = int(column_0 % 26 + 65) | |
# Change back to chr type and add to text | |
decrypted_msg += chr(integer) | |
# Repeat for the second column | |
column_1 = P[0][i] * C_inverse[1][0] + P[1][i] * C_inverse[1][1] | |
integer = int(column_1 % 26 + 65) | |
decrypted_msg += chr(integer) | |
if decrypted_msg[-1] == "0": | |
decrypted_msg = decrypted_msg[:-1] | |
return decrypted_msg | |
def find_multiplicative_inverse(determinant): | |
multiplicative_inverse = -1 | |
for i in range(26): | |
inverse = determinant * i | |
if inverse % 26 == 1: | |
multiplicative_inverse = i | |
break | |
return multiplicative_inverse | |
def make_key(): | |
# Make sure cipher determinant is relatively prime to 26 and only a/A - z/Z are given | |
determinant = 0 | |
C = None | |
while True: | |
cipher = input("Input 4 letter cipher: ") | |
C = create_matrix_of_integers_from_string(cipher) | |
determinant = C[0][0] * C[1][1] - C[0][1] * C[1][0] | |
determinant = determinant % 26 | |
inverse_element = find_multiplicative_inverse(determinant) | |
if inverse_element == -1: | |
print("Determinant is not relatively prime to 26, uninvertible key") | |
elif np.amax(C) > 26 and np.amin(C) < 0: | |
print("Only a-z characters are accepted") | |
print(np.amax(C), np.amin(C)) | |
else: | |
break | |
return C | |
def create_matrix_of_integers_from_string(string): | |
# Map string to a list of integers a/A <-> 0, b/B <-> 1 ... z/Z <-> 25 | |
integers = [chr_to_int(c) for c in string] | |
length = len(integers) | |
M = np.zeros((2, int(length / 2)), dtype=np.int32) | |
iterator = 0 | |
for column in range(int(length / 2)): | |
for row in range(2): | |
M[row][column] = integers[iterator] | |
iterator += 1 | |
return M | |
def chr_to_int(char): | |
# Uppercase the char to get into range 65-90 in ascii table | |
char = char.upper() | |
# Cast chr to int and subtract 65 to get 0-25 | |
integer = ord(char) - 65 | |
return integer | |
if __name__ == "__main__": | |
msg = input("Message: ") | |
encrypted_msg = encrypt(msg) | |
print(encrypted_msg) | |
decrypted_msg = decrypt(encrypted_msg) | |
print(decrypted_msg) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What do I have to type at "Input 4 letter cipher: "?