diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 47910e4eb..9efada3e2 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -41,7 +41,17 @@ https://www.youtube.com/watch?v=4RhLNDqcjpA 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,31 @@ class HillCipher: 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 +109,54 @@ class HillCipher: 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 +175,26 @@ class HillCipher: 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 @@ -176,4 +230,7 @@ def main(): if __name__ == "__main__": + import doctest + doctest.testmod() + main()