@@ -50,33 +50,34 @@ def replace_digits(self, num: int) -> str:
50
50
Convert numerical value to its character equivalent.
51
51
"""
52
52
return self .key_string [num ]
53
+
53
54
def integer_determinant (self , matrix : np .ndarray ) -> int :
54
55
"""
55
56
Calculate determinant of an integer matrix using exact arithmetic.
56
57
"""
57
58
n = matrix .shape [0 ]
58
-
59
+
59
60
# Base case for 1x1 matrix
60
61
if n == 1 :
61
62
return matrix [0 , 0 ]
62
-
63
+
63
64
# Base case for 2x2 matrix
64
65
if n == 2 :
65
66
return matrix [0 , 0 ] * matrix [1 , 1 ] - matrix [0 , 1 ] * matrix [1 , 0 ]
66
-
67
+
67
68
det = 0
68
69
for j in range (n ):
69
70
# Create minor matrix by removing first row and j-th column
70
71
minor = matrix [1 :, :]
71
72
minor = np .delete (minor , j , axis = 1 )
72
-
73
+
73
74
# Recursively calculate determinant of minor
74
75
minor_det = self .integer_determinant (minor )
75
-
76
+
76
77
# Calculate cofactor with sign
77
78
sign = (- 1 ) ** j
78
79
det += sign * matrix [0 , j ] * minor_det
79
-
80
+
80
81
return det
81
82
82
83
def check_determinant (self ) -> None :
@@ -89,7 +90,7 @@ def check_determinant(self) -> None:
89
90
ValueError: If determinant is not coprime with 36
90
91
"""
91
92
det = self .integer_determinant (self .encrypt_key )
92
-
93
+
93
94
# Ensure positive modulo value
94
95
det_mod = det % len (self .key_string )
95
96
if det_mod == 0 :
@@ -107,14 +108,14 @@ def process_text(self, text: str) -> str:
107
108
Prepare text for encryption/decryption.
108
109
"""
109
110
chars = [char for char in text .upper () if char in self .key_string ]
110
-
111
+
111
112
if not chars :
112
113
return ""
113
-
114
+
114
115
last = chars [- 1 ]
115
116
while len (chars ) % self .break_key != 0 :
116
117
chars .append (last )
117
-
118
+
118
119
return "" .join (chars )
119
120
120
121
def encrypt (self , text : str ) -> str :
@@ -124,17 +125,17 @@ def encrypt(self, text: str) -> str:
124
125
text = self .process_text (text .upper ())
125
126
if not text :
126
127
return ""
127
-
128
+
128
129
encrypted = ""
129
130
130
131
for i in range (0 , len (text ), self .break_key ):
131
- batch = text [i : i + self .break_key ]
132
+ batch = text [i : i + self .break_key ]
132
133
vec = [self .replace_letters (char ) for char in batch ]
133
134
batch_vec = np .array (vec ).reshape (- 1 , 1 )
134
-
135
+
135
136
product = self .encrypt_key @ batch_vec
136
137
batch_encrypted = self .modulus (product ).flatten ().astype (int )
137
-
138
+
138
139
encrypted_batch = "" .join (
139
140
self .replace_digits (num ) for num in batch_encrypted
140
141
)
@@ -149,7 +150,7 @@ def make_decrypt_key(self) -> np.ndarray:
149
150
n = self .break_key
150
151
det = self .integer_determinant (self .encrypt_key )
151
152
modulus = len (self .key_string )
152
-
153
+
153
154
# Find modular inverse of determinant
154
155
det_mod = det % modulus
155
156
det_inv = None
@@ -163,7 +164,7 @@ def make_decrypt_key(self) -> np.ndarray:
163
164
164
165
# Compute adjugate matrix
165
166
adjugate = np .zeros ((n , n ), dtype = int )
166
-
167
+
167
168
for i in range (n ):
168
169
for j in range (n ):
169
170
minor = np .delete (self .encrypt_key , i , axis = 0 )
@@ -183,18 +184,18 @@ def decrypt(self, text: str) -> str:
183
184
text = self .process_text (text .upper ())
184
185
if not text :
185
186
return ""
186
-
187
+
187
188
decrypt_key = self .make_decrypt_key ()
188
189
decrypted = ""
189
190
190
191
for i in range (0 , len (text ), self .break_key ):
191
- batch = text [i : i + self .break_key ]
192
+ batch = text [i : i + self .break_key ]
192
193
vec = [self .replace_letters (char ) for char in batch ]
193
194
batch_vec = np .array (vec ).reshape (- 1 , 1 )
194
-
195
+
195
196
product = decrypt_key @ batch_vec
196
197
batch_decrypted = self .modulus (product ).flatten ().astype (int )
197
-
198
+
198
199
decrypted_batch = "" .join (
199
200
self .replace_digits (num ) for num in batch_decrypted
200
201
)
@@ -203,7 +204,6 @@ def decrypt(self, text: str) -> str:
203
204
return decrypted
204
205
205
206
206
-
207
207
def main () -> None :
208
208
"""
209
209
Command-line interface for Hill Cipher operations.
@@ -213,14 +213,14 @@ def main() -> None:
213
213
214
214
print ("Enter each row of the encryption key with space separated integers" )
215
215
for i in range (n ):
216
- row = [int (x ) for x in input (f"Row { i + 1 } : " ).split ()]
216
+ row = [int (x ) for x in input (f"Row { i + 1 } : " ).split ()]
217
217
hill_matrix .append (row )
218
218
219
219
hc = HillCipher (np .array (hill_matrix ))
220
220
221
221
print ("\n Would you like to encrypt or decrypt some text?" )
222
222
option = input ("1. Encrypt\n 2. Decrypt\n Enter choice (1/2): " )
223
-
223
+
224
224
if option == "1" :
225
225
text = input ("\n Enter text to encrypt: " )
226
226
print ("\n Encrypted text:" )
@@ -235,20 +235,21 @@ def main() -> None:
235
235
236
236
if __name__ == "__main__" :
237
237
import doctest
238
+
238
239
doctest .testmod ()
239
-
240
+
240
241
print ("\n Running sample tests..." )
241
242
key = np .array ([[2 , 5 ], [1 , 6 ]])
242
243
cipher = HillCipher (key )
243
-
244
+
244
245
# Test encryption/decryption round trip
245
246
plaintext = "HELLO123"
246
247
encrypted = cipher .encrypt (plaintext )
247
248
decrypted = cipher .decrypt (encrypted )
248
-
249
+
249
250
print (f"\n Original text: { plaintext } " )
250
251
print (f"Encrypted text: { encrypted } " )
251
252
print (f"Decrypted text: { decrypted } " )
252
-
253
+
253
254
# Run CLI interface
254
255
main ()
0 commit comments