1111from numpy .typing import NDArray # Used for type hinting numpy arrays.
1212from typing import Any # Used for type hinting __getattr__ function.
1313from secrets import token_bytes # Used for generating random key if needed.
14+ from os .path import getsize # Used to acquire size of files.
15+ from os import remove # Used to remove files.
1416
1517
1618class AES :
@@ -138,7 +140,7 @@ def get(self, item: str) -> Any:
138140 :param item: Attribute to be retrieved. Valid attributes (running_mode, key, iv).
139141 :return: Attribute value.
140142 """
141- if item :
143+ if item in [ "running_mode" , "key" , "iv" ] :
142144 return self .__dict__ [f"_{ item } " ]
143145 else :
144146 raise AttributeError (f"No attribute <{ item } > exists!" )
@@ -157,17 +159,17 @@ def enc(self, *, data_string: str = "", file_path: str = "",
157159 if not running_mode :
158160 running_mode = self ._running_mode
159161 else :
160- self ._running_mode = running_mode
162+ self .set ( "running_mode" , running_mode )
161163
162164 if not key :
163165 key = self ._key
164166 else :
165- self ._key = key
167+ self .set ( "key" , key )
166168
167169 if not iv :
168170 iv = self ._iv
169171 else :
170- self ._iv = iv
172+ self .set ( "iv" , iv )
171173
172174 if data_string :
173175 if running_mode == "ECB" :
@@ -176,8 +178,17 @@ def enc(self, *, data_string: str = "", file_path: str = "",
176178 return self .__cbc_enc (data_string = data_string , keys = self .key_expand (key ), iv = iv )
177179 else :
178180 raise NotImplementedError (f"{ running_mode } is not supported!" )
181+ elif file_path :
182+ if running_mode == "ECB" :
183+ self .__ecb_enc (file_path = file_path , keys = self .key_expand (key ))
184+ return ""
185+ elif running_mode == "CBC" :
186+ self .__cbc_enc (file_path = file_path , keys = self .key_expand (key ), iv = iv )
187+ return ""
188+ else :
189+ raise NotImplementedError (f"{ running_mode } is not supported!" )
179190 else :
180- raise NotImplementedError ( "File encryption is not implemented yet ..." )
191+ raise RuntimeWarning ( "No file or string was give ..." )
181192
182193 def dec (self , * , data_string : str = "" , file_path : str = "" ,
183194 running_mode : str = "" , key : str = "" , iv : str = "" ) -> str :
@@ -193,17 +204,17 @@ def dec(self, *, data_string: str = "", file_path: str = "",
193204 if not running_mode :
194205 running_mode = self ._running_mode
195206 else :
196- self ._running_mode = running_mode
207+ self .set ( "running_mode" , running_mode )
197208
198209 if not key :
199210 key = self ._key
200211 else :
201- self ._key = key
212+ self .set ( "key" , key )
202213
203214 if not iv :
204215 iv = self ._iv
205216 else :
206- self ._iv = iv
217+ self .set ( "iv" , iv )
207218
208219 if data_string :
209220 if running_mode == "ECB" :
@@ -212,8 +223,17 @@ def dec(self, *, data_string: str = "", file_path: str = "",
212223 return self .__cbc_dec (data_string = data_string , keys = self .key_expand (key ), iv = iv )
213224 else :
214225 raise NotImplementedError (f"{ running_mode } is not supported!" )
226+ elif file_path :
227+ if running_mode == "ECB" :
228+ self .__ecb_dec (file_path = file_path , keys = self .key_expand (key ))
229+ return ""
230+ elif running_mode == "CBC" :
231+ self .__cbc_dec (file_path = file_path , keys = self .key_expand (key ), iv = iv )
232+ return ""
233+ else :
234+ raise NotImplementedError (f"{ running_mode } is not supported!" )
215235 else :
216- raise NotImplementedError ( "File encryption is not implemented yet ..." )
236+ raise RuntimeWarning ( "No file or string was give ..." )
217237
218238 @classmethod
219239 def __ecb_enc (cls , * , data_string : str = "" , file_path : str = "" , keys : NDArray [np .uint8 ]) -> str :
@@ -234,7 +254,7 @@ def __ecb_enc(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[
234254 enc : str = "" .join (cls .vec_chr (cls .__enc_schedule (raw , keys ).flatten ().astype (np .uint8 )))
235255 output_string += enc
236256
237- extra = len (data_string ) % 16 # Calculates length of final data block
257+ extra = len (data_string ) % 16 # Calculates length of final data block
238258 result : str = ""
239259
240260 if extra != 0 : # If last data block not integer multiple of 16 adds extra padding
@@ -244,8 +264,33 @@ def __ecb_enc(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[
244264 result = "" .join (cls .vec_chr (cls .__enc_schedule (raw .reshape (4 , 4 ), keys ).flatten ().astype (np .uint8 )))
245265
246266 return output_string + result
267+ elif file_path :
268+ file_size = getsize (file_path )
269+
270+ with open (f"{ file_path } .enc" , 'wb' ) as output , open (file_path , 'rb' ) as data :
271+ for i in range (int (file_size / 16 )):
272+ raw = np .array ([i for i in data .read (16 )], dtype = np .uint8 ).reshape (4 , 4 )
273+ out = bytes ((cls .__enc_schedule (raw , keys ).flatten ()).tolist ())
274+ output .write (out )
275+
276+ if file_size % 16 != 0 :
277+ raw_l = [i for i in data .read ()]
278+ raw_l , length = cls .__add_padding (raw_l )
279+
280+ out = bytes ((cls .__enc_schedule (np .array (raw_l ).reshape (4 , 4 ), keys ).flatten ()).tolist ())
281+ identifier = bytes ((cls .__enc_schedule (np .array ([0 for i in range (15 )] + [length ]).reshape (4 , 4 ),
282+ keys ).flatten ()).tolist ())
283+
284+ output .write (out + identifier )
285+ else :
286+ identifier = bytes ((cls .__enc_schedule (np .array ([0 for i in range (16 )]).reshape (4 , 4 ),
287+ keys ).flatten ()).tolist ())
288+ output .write (identifier )
289+
290+ remove (file_path )
291+ return ""
247292 else :
248- raise NotImplementedError
293+ raise RuntimeError ( "No string or file path received...?" )
249294
250295 @classmethod
251296 def __ecb_dec (cls , * , data_string : str = "" , file_path : str = "" , keys : NDArray [np .uint8 ]) -> str :
@@ -268,16 +313,161 @@ def __ecb_dec(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[
268313 output_string += dec
269314
270315 return output_string
316+ elif file_path :
317+ file_size = getsize (file_path )
318+ file_name = file_path [:- 4 ]
319+
320+ with open (f"{ file_name } " , 'wb' ) as output , open (file_path , 'rb' ) as data :
321+ for i in range (int (file_size / 16 ) - 2 ):
322+ raw = np .array ([i for i in data .read (16 )]).reshape (4 , 4 )
323+ result = bytes ((cls .__dec_schedule (raw , keys ).flatten ()).tolist ())
324+ output .write (result )
325+
326+ data_final = np .array ([i for i in data .read (16 )]).reshape (4 , 4 )
327+ identifier = np .array ([i for i in data .read ()]).reshape (4 , 4 )
328+
329+ data_dec = (cls .__dec_schedule (data_final , keys ).flatten ()).tolist ()
330+ id_l = (cls .__dec_schedule (identifier , keys ).flatten ()).tolist ()
331+
332+ result = bytes (cls .__remove_padding (data_dec , id_l ))
333+
334+ output .write (result )
335+
336+ remove (file_path )
337+ return ""
271338 else :
272- raise NotImplementedError
339+ raise RuntimeError ( "No string or file path received...?" )
273340
274341 @classmethod
275342 def __cbc_enc (cls , * , data_string : str = "" , file_path : str = "" , keys : NDArray [np .uint8 ], iv : str ) -> str :
276- raise NotImplementedError ("CBC encryption not yet implemented..." )
343+ """
344+ Preforms CBC encryption instructions on specified file or data string.
345+ :param data_string: Data string to be encrypted.
346+ :param file_path: Path to file that is encrypted.
347+ :param keys: Key used for encryption.
348+ :param iv: Initialization vector used.
349+ :return: Data string or writes encrypted file.
350+ """
351+ enc_array : NDArray [np .uint8 ] = np .frombuffer (bytes .fromhex (iv ), dtype = np .uint8 ).reshape (4 , 4 )
352+
353+ if data_string :
354+ output_string : str = ""
355+
356+ for i in range (len (data_string ) // 16 ): # Encryption cycle, skips last if not integer multiple of 16 bytes
357+ raw : NDArray [np .uint8 ] = np .array ([ord (i ) for i in data_string [(i * 16 ): ((i + 1 ) * 16 )]],
358+ dtype = np .uint8 ).reshape (4 , 4 )
359+ enc = np .bitwise_xor (raw , enc_array ) # Xor operation with previous encrypted block or iv
360+ enc_array = cls .__enc_schedule (enc , keys )
361+ output_string += "" .join (cls .vec_chr (enc_array .flatten ().astype (np .uint8 )))
362+
363+ extra = len (data_string ) % 16 # Calculates length of final data block
364+ result : str = ""
365+
366+ if extra != 0 : # If last data block not integer multiple of 16 adds extra padding
367+ raw = np .full (16 , 0 , dtype = np .uint8 )
368+ raw [:extra ] = np .array ([ord (i ) for i in data_string ][- 1 * extra :], dtype = np .uint8 )
369+ raw = raw .reshape (4 , 4 )
370+
371+ temp_array = np .bitwise_xor (raw , enc_array ) # Xor operation with previous encrypted block
372+
373+ result = "" .join (cls .vec_chr (cls .__enc_schedule (temp_array , keys ).flatten ().astype (np .uint8 )))
374+
375+ return output_string + result
376+ elif file_path :
377+ file_size = getsize (file_path )
378+
379+ with open (f"{ file_path } .enc" , 'wb' ) as output , open (file_path , 'rb' ) as data :
380+ for i in range (int (file_size / 16 )):
381+ raw = np .array ([i for i in data .read (16 )]).reshape (4 , 4 )
382+ raw = np .bitwise_xor (raw , enc_array )
383+ enc_array = cls .__enc_schedule (raw , keys )
384+ output .write (bytes ((enc_array .flatten ()).tolist ()))
385+
386+ if file_size % 16 != 0 :
387+ final = [i for i in data .read ()]
388+ final , length = cls .__add_padding (final )
389+
390+ raw = np .bitwise_xor (np .array (final ).reshape (4 , 4 ), enc_array )
391+ enc_array = cls .__enc_schedule (raw , keys )
392+
393+ identifier = np .bitwise_xor (np .array ([0 for i in range (15 )] + [length ]).reshape (4 , 4 ), enc_array )
394+ identifier = cls .__enc_schedule (identifier , keys )
395+
396+ output .write (bytes ((enc_array .flatten ()).tolist () + (identifier .flatten ()).tolist ()))
397+ else :
398+ identifier = np .bitwise_xor (np .array ([0 for i in range (16 )]).reshape (4 , 4 ), enc_array )
399+ id_bytes = bytes (((cls .__enc_schedule (identifier , keys )).flatten ()).tolist ())
400+ output .write (id_bytes )
401+ remove (file_path )
402+ return ""
403+ else :
404+ raise RuntimeError ("No string or file path received...?" )
277405
278406 @classmethod
279407 def __cbc_dec (cls , * , data_string : str = "" , file_path : str = "" , keys : NDArray [np .uint8 ], iv : str ) -> str :
280- raise NotImplementedError ("CBC decryption not yet implemented..." )
408+ """
409+ Preforms CBC decryption instructions on specified file or data string.
410+ :param data_string: Data string to be decrypted.
411+ :param file_path: Path to file that is decrypted.
412+ :param keys: Key used for decryption.
413+ :param iv: Initialization vector used.
414+ :return: Data string or writes decrypted file.
415+ """
416+ dec_array : NDArray [np .uint8 ] = np .frombuffer (bytes .fromhex (iv ), dtype = np .uint8 ).reshape (4 , 4 )
417+
418+ if data_string :
419+ output_string : str = ""
420+
421+ for i in range (len (data_string ) // 16 ): # Decryption cycle
422+ raw : NDArray [np .uint8 ] = np .array ( # Reads in input string 16 bytes at a time
423+ [ord (i ) for i in data_string [(i * 16 ): ((i + 1 ) * 16 )]], dtype = np .uint8 ).reshape (4 , 4 )
424+
425+ temp_array = cls .__dec_schedule (raw , keys )
426+ result = np .bitwise_xor (temp_array , dec_array )
427+ dec_array = raw
428+
429+ dec = "" .join (cls .vec_chr (result .flatten ().astype (np .uint8 )))
430+ output_string += dec
431+
432+ return output_string
433+ elif file_path :
434+ file_size = getsize (file_path )
435+ file_name = file_path [:- 4 ]
436+
437+ with open (f"{ file_name } " , 'wb' ) as output , open (file_path , 'rb' ) as data :
438+ if int (file_size / 16 ) - 3 >= 0 :
439+ vector = np .array ([i for i in data .read (16 )]).reshape (4 , 4 )
440+ raw = cls .__dec_schedule (vector , keys )
441+ result = np .bitwise_xor (raw , dec_array )
442+ output .write (bytes ((result .flatten ()).tolist ()))
443+
444+ for i in range (int (file_size / 16 ) - 3 ):
445+ raw = np .array ([i for i in data .read (16 )]).reshape (4 , 4 )
446+ result = cls .__dec_schedule (raw , keys )
447+ result = np .bitwise_xor (result , vector )
448+ vector = raw
449+ output .write (bytes ((result .flatten ()).tolist ()))
450+ else :
451+ vector = dec_array
452+
453+ data_pice = np .array ([i for i in data .read (16 )]).reshape (4 , 4 )
454+ vector_1 , identifier = data_pice , np .array ([i for i in data .read ()]).reshape (4 , 4 )
455+
456+ result = cls .__dec_schedule (data_pice , keys )
457+ identifier = cls .__dec_schedule (identifier , keys )
458+
459+ identifier = np .bitwise_xor (identifier , vector_1 )
460+ data_pice = np .bitwise_xor (result , vector )
461+
462+ result_bytes = bytes (cls .__remove_padding ((data_pice .flatten ()).tolist (),
463+ (identifier .flatten ()).tolist ()))
464+
465+ output .write (result_bytes )
466+
467+ remove (file_path )
468+ return ""
469+ else :
470+ raise RuntimeError ("No string or file path received...?" )
281471
282472 @staticmethod
283473 def key_gen (length : int = 16 ) -> str :
@@ -473,3 +663,21 @@ def __mix_columns(matrix: NDArray[np.uint8], shift: int) -> NDArray[np.uint8]:
473663 result [j , i ] = temp
474664
475665 return result .transpose ()
666+
667+ @staticmethod
668+ def __add_padding (data : list [int ]) -> tuple [list [int ], int ]:
669+ """Adds a padding to ensure a bloke size of 16 bytes."""
670+ length = 16 - len (data )
671+ for i in range (length ):
672+ data .append (0 )
673+ return data , length
674+
675+ @staticmethod
676+ def __remove_padding (data : list [int ], identifier : list [int ]) -> list [int ]:
677+ """Removes the padding from the data using information from identifier."""
678+ if identifier [- 1 ] == 0 :
679+ return data
680+ elif 0 < identifier [- 1 ] < 16 :
681+ return data [:- identifier [- 1 ]]
682+ else :
683+ raise ValueError ('Invalid padding' )
0 commit comments