From 38d2e98665db0df90ba1ef33d9ba8e5025c7e38d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 18 May 2020 13:05:51 +0200 Subject: [PATCH] hill_cipher.py: gcd() -> greatest_common_divisor() (#1997) * hill_cipher.py: gcd() -> greatest_common_divisor() * fixup! Format Python code with psf/black push * import string * updating DIRECTORY.md * Change matrix to array Add more tests Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: John Law --- DIRECTORY.md | 1 + ciphers/hill_cipher.py | 107 ++++++++++++++++++++--------------------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index f4499d8e0..711d0a9e9 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -603,6 +603,7 @@ * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) * [Sleep Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/sleep_sort.py) * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) + * [Strand Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/strand_sort.py) * [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) * [Topological Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/topological_sort.py) * [Tree Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tree_sort.py) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 9efada3e2..9cd4a73b4 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -1,10 +1,9 @@ """ 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 algorithm, you have an encryption key matrix. This is what will be used -in encoding and decoding your text. +The 'HillCipher' class below implements the Hill Cipher algorithm which uses +modern linear algebra techniques to encode and decode text using an encryption +key matrix. Algorithm: Let the order of the encryption key be N (as it is a square matrix). @@ -24,12 +23,11 @@ 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. +This implementation only considers 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 @@ -38,67 +36,66 @@ https://www.youtube.com/watch?v=4RhLNDqcjpA """ +import string import numpy -def gcd(a: int, b: int) -> int: +def greatest_common_divisor(a: int, b: int) -> int: """ - >>> gcd(4, 8) + >>> greatest_common_divisor(4, 8) 4 - >>> gcd(8, 4) + >>> greatest_common_divisor(8, 4) 4 - >>> gcd(4, 7) + >>> greatest_common_divisor(4, 7) 1 - >>> gcd(0, 10) + >>> greatest_common_divisor(0, 10) 10 """ - if a == 0: - return b - return gcd(b % a, a) + return b if a == 0 else greatest_common_divisor(b % a, a) class HillCipher: - key_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + key_string = string.ascii_uppercase + string.digits # This cipher takes alphanumerics into account # i.e. a total of 36 characters # take x and return x % len(key_string) modulus = numpy.vectorize(lambda x: x % 36) - toInt = numpy.vectorize(lambda x: round(x)) + to_int = numpy.vectorize(lambda x: round(x)) def __init__(self, encrypt_key): """ - encrypt_key is an NxN numpy matrix + encrypt_key is an NxN numpy array """ self.encrypt_key = self.modulus(encrypt_key) # mod36 calc's on the encrypt key self.check_determinant() # validate the determinant of the encryption key self.decrypt_key = None self.break_key = encrypt_key.shape[0] - def replaceLetters(self, letter: str) -> int: + def replace_letters(self, letter: str) -> int: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) - >>> hill_cipher.replaceLetters('T') + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher.replace_letters('T') 19 - >>> hill_cipher.replaceLetters('0') + >>> hill_cipher.replace_letters('0') 26 """ return self.key_string.index(letter) - def replaceNumbers(self, num: int) -> str: + def replace_digits(self, num: int) -> str: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) - >>> hill_cipher.replaceNumbers(19) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) + >>> hill_cipher.replace_digits(19) 'T' - >>> hill_cipher.replaceNumbers(26) + >>> hill_cipher.replace_digits(26) '0' """ return self.key_string[round(num)] def check_determinant(self) -> None: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.check_determinant() """ det = round(numpy.linalg.det(self.encrypt_key)) @@ -107,19 +104,20 @@ class HillCipher: det = det % len(self.key_string) req_l = len(self.key_string) - if gcd(det, len(self.key_string)) != 1: + if greatest_common_divisor(det, len(self.key_string)) != 1: raise ValueError( f"determinant modular {req_l} of encryption key({det}) is not co prime w.r.t {req_l}.\nTry another key." ) def process_text(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.process_text('Testing Hill Cipher') 'TESTINGHILLCIPHERR' + >>> hill_cipher.process_text('hello') + 'HELLOO' """ - text = list(text.upper()) - chars = [char for char in text if char in self.key_string] + chars = [char for char in text.upper() if char in self.key_string] last = chars[-1] while len(chars) % self.break_key != 0: @@ -129,22 +127,24 @@ class HillCipher: def encrypt(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.encrypt('testing hill cipher') 'WHXYJOLM9C6XT085LL' + >>> hill_cipher.encrypt('hello') + '85FF00' """ text = self.process_text(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 = [self.replaceLetters(char) for char in batch] - batch_vec = numpy.matrix([batch_vec]).T + batch_vec = [self.replace_letters(char) for char in batch] + batch_vec = numpy.array([batch_vec]).T batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[ 0 ] encrypted_batch = "".join( - self.replaceNumbers(num) for num in batch_encrypted + self.replace_digits(num) for num in batch_encrypted ) encrypted += encrypted_batch @@ -152,10 +152,10 @@ class HillCipher: def make_decrypt_key(self): """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.make_decrypt_key() - matrix([[ 6., 25.], - [ 5., 26.]]) + array([[ 6., 25.], + [ 5., 26.]]) """ det = round(numpy.linalg.det(self.encrypt_key)) @@ -173,13 +173,15 @@ class HillCipher: * numpy.linalg.inv(self.encrypt_key) ) - return self.toInt(self.modulus(inv_key)) + return self.to_int(self.modulus(inv_key)) def decrypt(self, text: str) -> str: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL') 'TESTINGHILLCIPHERR' + >>> hill_cipher.decrypt('85FF00') + 'HELLOO' """ self.decrypt_key = self.make_decrypt_key() text = self.process_text(text.upper()) @@ -187,13 +189,13 @@ class HillCipher: for i in range(0, len(text) - self.break_key + 1, self.break_key): batch = text[i : i + self.break_key] - batch_vec = [self.replaceLetters(char) for char in batch] - batch_vec = numpy.matrix([batch_vec]).T + batch_vec = [self.replace_letters(char) for char in batch] + batch_vec = numpy.array([batch_vec]).T batch_decrypted = self.modulus(self.decrypt_key.dot(batch_vec)).T.tolist()[ 0 ] decrypted_batch = "".join( - self.replaceNumbers(num) for num in batch_decrypted + self.replace_digits(num) for num in batch_decrypted ) decrypted += decrypted_batch @@ -206,19 +208,13 @@ def main(): print("Enter each row of the encryption key with space separated integers") for i in range(N): - row = list(map(int, input().split())) + row = [int(x) for x in input().split()] hill_matrix.append(row) - hc = HillCipher(numpy.matrix(hill_matrix)) + hc = HillCipher(numpy.array(hill_matrix)) print("Would you like to encrypt or decrypt some text? (1 or 2)") - option = input( - """ -1. Encrypt -2. Decrypt -""" - ) - + option = input("\n1. Encrypt\n2. Decrypt\n") if option == "1": text_e = input("What text would you like to encrypt?: ") print("Your encrypted text is:") @@ -231,6 +227,7 @@ def main(): if __name__ == "__main__": import doctest + doctest.testmod() main()