mirror of
https://github.com/TheAlgorithms/Python.git
synced 2025-01-18 16:27:02 +00:00
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 <johnlaw.po@gmail.com>
This commit is contained in:
parent
aa120cea12
commit
38d2e98665
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue
Block a user