From a0bbc6737a0ba46a7f5a6e1b670641538d3f67d4 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 18 May 2020 11:00:21 +0200 Subject: [PATCH 1/5] hill_cipher.py: gcd() -> greatest_common_divisor() --- ciphers/hill_cipher.py | 74 ++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 9efada3e2b7d..f4fda5d798b3 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 @@ 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,34 +36,33 @@ """ +from string import ascii_upper 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): """ @@ -76,22 +73,22 @@ def __init__(self, encrypt_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.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.replace_digits(19) 'T' - >>> hill_cipher.replaceNumbers(26) + >>> hill_cipher.replace_digits(26) '0' """ return self.key_string[round(num)] @@ -107,7 +104,7 @@ def check_determinant(self) -> None: 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." ) @@ -118,8 +115,7 @@ def process_text(self, text: str) -> str: >>> hill_cipher.process_text('Testing Hill Cipher') 'TESTINGHILLCIPHERR' """ - 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: @@ -138,13 +134,13 @@ def encrypt(self, text: str) -> str: 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 = [self.replace_letters(char) for char in batch] batch_vec = numpy.matrix([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 @@ -173,7 +169,7 @@ def make_decrypt_key(self): * 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: """ @@ -187,13 +183,13 @@ def decrypt(self, text: str) -> str: 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 = [self.replace_letters(char) for char in batch] batch_vec = numpy.matrix([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 +202,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)) 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:") From 3d8eb364c6b795abcaf387ae168d3e0ee53e93f9 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Mon, 18 May 2020 09:01:26 +0000 Subject: [PATCH 2/5] fixup! Format Python code with psf/black push --- ciphers/hill_cipher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index f4fda5d798b3..4b9b9501426d 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -36,7 +36,7 @@ """ -from string import ascii_upper +from string import ascii_upper import numpy @@ -221,6 +221,7 @@ def main(): if __name__ == "__main__": import doctest + doctest.testmod() main() From c5ca569d7bbbcda0794bc11039eda3322e2f3461 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 18 May 2020 11:11:42 +0200 Subject: [PATCH 3/5] import string --- ciphers/hill_cipher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 4b9b9501426d..3aa177b921b6 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -36,7 +36,7 @@ """ -from string import ascii_upper +import string import numpy From 696a3d0d2941d28f6173dc39677c0e76a957f267 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Mon, 18 May 2020 09:12:02 +0000 Subject: [PATCH 4/5] updating DIRECTORY.md --- DIRECTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index f4499d8e07bd..711d0a9e972f 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) From 36c01f965b3e877d5707887f2008dd5a12005843 Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 18 May 2020 11:47:22 +0200 Subject: [PATCH 5/5] Change matrix to array Add more tests --- ciphers/hill_cipher.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 3aa177b921b6..9cd4a73b4f44 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -25,7 +25,7 @@ Note: 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 +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. @@ -66,7 +66,7 @@ class HillCipher: 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 @@ -75,7 +75,7 @@ def __init__(self, encrypt_key): def replace_letters(self, letter: str) -> int: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.replace_letters('T') 19 >>> hill_cipher.replace_letters('0') @@ -85,7 +85,7 @@ def replace_letters(self, letter: str) -> int: def replace_digits(self, num: int) -> str: """ - >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.replace_digits(19) 'T' >>> hill_cipher.replace_digits(26) @@ -95,7 +95,7 @@ def replace_digits(self, num: int) -> str: 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)) @@ -111,9 +111,11 @@ def check_determinant(self) -> None: 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' """ chars = [char for char in text.upper() if char in self.key_string] @@ -125,9 +127,11 @@ def process_text(self, text: str) -> str: 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 = "" @@ -135,7 +139,7 @@ def encrypt(self, text: str) -> str: for i in range(0, len(text) - self.break_key + 1, self.break_key): batch = text[i : i + self.break_key] batch_vec = [self.replace_letters(char) for char in batch] - batch_vec = numpy.matrix([batch_vec]).T + batch_vec = numpy.array([batch_vec]).T batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[ 0 ] @@ -148,10 +152,10 @@ def encrypt(self, text: str) -> str: 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,9 +177,11 @@ def make_decrypt_key(self): 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()) @@ -184,7 +190,7 @@ def decrypt(self, text: str) -> str: for i in range(0, len(text) - self.break_key + 1, self.break_key): batch = text[i : i + self.break_key] batch_vec = [self.replace_letters(char) for char in batch] - batch_vec = numpy.matrix([batch_vec]).T + batch_vec = numpy.array([batch_vec]).T batch_decrypted = self.modulus(self.decrypt_key.dot(batch_vec)).T.tolist()[ 0 ] @@ -205,7 +211,7 @@ def main(): 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("\n1. Encrypt\n2. Decrypt\n")