From 51d89deb9c80ee96d120ec061338b0efed51d69e Mon Sep 17 00:00:00 2001 From: Bharath Kotha Date: Sun, 17 May 2020 12:01:30 +0530 Subject: [PATCH 1/3] Added tests and type hints to hill cipher --- ciphers/hill_cipher.py | 95 +++++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 47910e4ebdaa..b28d6ef50575 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -41,7 +41,17 @@ import numpy -def gcd(a, b): +def gcd(a: int, b: int) -> int: + """ + >>> gcd(4, 8) + 4 + >>> gcd(8, 4) + 4 + >>> gcd(4, 7) + 1 + >>> gcd(0, 10) + 10 + """ if a == 0: return b return gcd(b % a, a) @@ -52,9 +62,6 @@ class HillCipher: # 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) @@ -69,7 +76,32 @@ def __init__(self, encrypt_key): self.decrypt_key = None self.break_key = encrypt_key.shape[0] - def check_determinant(self): + def replaceLetters(self, letter: str) -> int: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.replaceLetters('T') + 19 + >>> hill_cipher.replaceLetters('0') + 26 + """ + return self.key_string.index(letter) + + def replaceNumbers(self, num: int) -> str: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.replaceNumbers(19) + 'T' + >>> hill_cipher.replaceNumbers(26) + '0' + """ + return self.key_string[round(num)] + + def check_determinant(self) -> None: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.check_determinant() + >>> + """ det = round(numpy.linalg.det(self.encrypt_key)) if det < 0: @@ -78,38 +110,54 @@ def check_determinant(self): req_l = len(self.key_string) if gcd(det, len(self.key_string)) != 1: raise ValueError( - "discriminant modular {} of encryption key({}) is not co prime w.r.t {}.\nTry another key.".format( - req_l, det, req_l - ) + 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): + def process_text(self, text: str) -> str: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.process_text('Testing Hill Cipher') + 'TESTINGHILLCIPHERR' + """ text = list(text.upper()) - text = [char for char in text if char in self.key_string] + chars = [char for char in text if char in self.key_string] - last = text[-1] - while len(text) % self.break_key != 0: - text.append(last) + last = chars[-1] + while len(chars) % self.break_key != 0: + chars.append(last) - return "".join(text) + return "".join(chars) - def encrypt(self, text): + def encrypt(self, text: str) -> str: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.encrypt('testing hill cipher') + 'WHXYJOLM9C6XT085LL' + """ 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 = list(map(self.replaceLetters, batch)) + batch_vec = [self.replaceLetters(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(list(map(self.replaceNumbers, batch_encrypted))) + encrypted_batch = "".join( + self.replaceNumbers(num) for num in batch_encrypted + ) encrypted += encrypted_batch return encrypted def make_decrypt_key(self): + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.make_decrypt_key() + matrix([[ 6., 25.], + [ 5., 26.]]) + """ det = round(numpy.linalg.det(self.encrypt_key)) if det < 0: @@ -128,19 +176,26 @@ def make_decrypt_key(self): return self.toInt(self.modulus(inv_key)) - def decrypt(self, text): + def decrypt(self, text: str) -> str: + """ + >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) + >>> hill_cipher.decrypt('WHXYJOLM9C6XT085LL') + 'TESTINGHILLCIPHERR' + """ self.decrypt_key = self.make_decrypt_key() text = self.process_text(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 = [self.replaceLetters(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(list(map(self.replaceNumbers, batch_decrypted))) + decrypted_batch = "".join( + self.replaceNumbers(num) for num in batch_decrypted + ) decrypted += decrypted_batch return decrypted From f84ccefe29182bdd37ce6e4d5046b06e8a867341 Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 18 May 2020 02:24:48 +0200 Subject: [PATCH 2/3] Remove extra >>> --- ciphers/hill_cipher.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index b28d6ef50575..17823370b361 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -100,7 +100,6 @@ def check_determinant(self) -> None: """ >>> hill_cipher = HillCipher(numpy.matrix([[2, 5], [1, 6]])) >>> hill_cipher.check_determinant() - >>> """ det = round(numpy.linalg.det(self.encrypt_key)) From 2c0844ac27819e65b5fd8a2444536ee0b341010a Mon Sep 17 00:00:00 2001 From: John Law Date: Mon, 18 May 2020 02:25:43 +0200 Subject: [PATCH 3/3] import doctest --- ciphers/hill_cipher.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 17823370b361..9efada3e2b7d 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -230,4 +230,7 @@ def main(): if __name__ == "__main__": + import doctest + doctest.testmod() + main()