Skip to content

Commit e09fa4b

Browse files
authored
Update hill_cipher.py
1 parent d060dda commit e09fa4b

1 file changed

Lines changed: 40 additions & 26 deletions

File tree

ciphers/hill_cipher.py

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,90 +4,104 @@
44
from maths.greatest_common_divisor import greatest_common_divisor
55

66
class HillCipher:
7-
key_string = string.ascii_uppercase + string.digits # 36 alphanumeric chars
8-
modulus = np.vectorize(lambda x: x % 36) # Mod 36 operation
9-
to_int = np.vectorize(round) # Round to nearest integer
7+
key_string = string.ascii_uppercase + string.digits # 36 chars
8+
modulus = np.vectorize(lambda x: x % 36) # Mod 36
9+
to_int = np.vectorize(round) # Round numbers
1010

1111
def __init__(self, encrypt_key: np.ndarray) -> None:
1212
self.encrypt_key = self.modulus(encrypt_key)
13-
self.check_determinant() # Validate key determinant
14-
self.break_key = encrypt_key.shape[0] # Matrix order
13+
self.check_determinant() # Validate key
14+
self.break_key = encrypt_key.shape[0] # Matrix size
1515

1616
def replace_letters(self, letter: str) -> int:
17-
"""Map char to index (A=0, Z=25, 0=26, 9=35)"""
17+
"""Char to index (A=0, 0=26)"""
1818
return self.key_string.index(letter)
1919

2020
def replace_digits(self, num: int) -> str:
21-
"""Map index back to char"""
21+
"""Index to char"""
2222
return self.key_string[num]
2323

2424
def check_determinant(self) -> None:
25-
"""Ensure det(key) is coprime with 36"""
25+
"""Ensure det(key) coprime with 36"""
2626
det = round(np.linalg.det(self.encrypt_key))
2727
if det < 0:
2828
det %= len(self.key_string)
29-
29+
3030
error_msg = f"Det {det} not coprime with 36. Try another key."
3131
if greatest_common_divisor(det, len(self.key_string)) != 1:
3232
raise ValueError(error_msg)
3333

3434
def process_text(self, text: str) -> str:
35-
"""Convert to uppercase, remove invalid chars, pad to multiple of break_key"""
35+
"""Uppercase, filter, pad text"""
3636
chars = [c for c in text.upper() if c in self.key_string]
3737
last = chars[-1] if chars else 'A'
3838
while len(chars) % self.break_key != 0:
3939
chars.append(last)
4040
return "".join(chars)
4141

4242
def encrypt(self, text: str) -> str:
43-
"""Encrypt text using Hill cipher"""
43+
"""Encrypt with Hill cipher"""
4444
text = self.process_text(text.upper())
4545
encrypted = ""
4646
for i in range(0, len(text), self.break_key):
4747
batch = text[i:i+self.break_key]
4848
vec = [self.replace_letters(c) for c in batch]
4949
batch_vec = np.array([vec]).T
50-
batch_encrypted = self.modulus(self.encrypt_key.dot(batch_vec)).T.tolist()[0]
51-
encrypted += "".join(self.replace_digits(round(n)) for n in batch_encrypted
50+
product = self.encrypt_key.dot(batch_vec)
51+
modulated = self.modulus(product)
52+
batch_encrypted = modulated.T.tolist()[0]
53+
encrypted_batch = "".join(
54+
self.replace_digits(round(n)) for n in batch_encrypted
55+
)
56+
encrypted += encrypted_batch
5257
return encrypted
5358

5459
def make_decrypt_key(self) -> np.ndarray:
55-
"""Calculate decryption key matrix"""
60+
"""Create decryption key"""
5661
det = round(np.linalg.det(self.encrypt_key))
5762
if det < 0:
5863
det %= len(self.key_string)
59-
60-
# Find modular inverse of det
64+
65+
# Find det modular inverse
6166
det_inv = next(i for i in range(36) if (det * i) % 36 == 1)
62-
63-
# Calculate inverse key
64-
inv_key = det_inv * np.linalg.det(self.encrypt_key) * np.linalg.inv(self.encrypt_key)
67+
68+
# Compute inverse key
69+
inv_key = (
70+
det_inv *
71+
np.linalg.det(self.encrypt_key) *
72+
np.linalg.inv(self.encrypt_key)
73+
)
6574
return self.to_int(self.modulus(inv_key))
6675

6776
def decrypt(self, text: str) -> str:
68-
"""Decrypt text using Hill cipher"""
77+
"""Decrypt with Hill cipher"""
6978
decrypt_key = self.make_decrypt_key()
7079
text = self.process_text(text.upper())
7180
decrypted = ""
7281
for i in range(0, len(text), self.break_key):
7382
batch = text[i:i+self.break_key]
7483
vec = [self.replace_letters(c) for c in batch]
7584
batch_vec = np.array([vec]).T
76-
batch_decrypted = self.modulus(decrypt_key.dot(batch_vec)).T.tolist()[0]
77-
decrypted += "".join(self.replace_digits(round(n)) for n in batch_decrypted
85+
product = decrypt_key.dot(batch_vec)
86+
modulated = self.modulus(product)
87+
batch_decrypted = modulated.T.tolist()[0]
88+
decrypted_batch = "".join(
89+
self.replace_digits(round(n)) for n in batch_decrypted
90+
)
91+
decrypted += decrypted_batch
7892
return decrypted
7993

8094
def main() -> None:
81-
"""CLI for Hill Cipher"""
95+
"""Hill Cipher CLI"""
8296
n = int(input("Enter key order: "))
8397
print(f"Enter {n} rows of space-separated integers:")
8498
matrix = [list(map(int, input().split())) for _ in range(n)]
85-
99+
86100
hc = HillCipher(np.array(matrix))
87-
101+
88102
option = input("1. Encrypt\n2. Decrypt\nChoose: ")
89103
text = input("Enter text: ")
90-
104+
91105
if option == "1":
92106
print("Encrypted:", hc.encrypt(text))
93107
elif option == "2":

0 commit comments

Comments
 (0)