Skip to content

Commit ed2a9b5

Browse files
authored
Merge pull request #72 from Glindeb/dev
Dev
2 parents 9f331e2 + 6fe925e commit ed2a9b5

File tree

8 files changed

+322
-33
lines changed

8 files changed

+322
-33
lines changed

.github/workflows/publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ jobs:
116116
GITHUB_TOKEN: ${{ secrets.RELEASE }}
117117
run: >-
118118
gh release create
119-
'v1.5.4'
119+
'v1.5.5'
120120
--repo '${{ github.repository }}'
121121
--generate-notes
122122
- name: Upload artifact signatures to GitHub Release
@@ -127,5 +127,5 @@ jobs:
127127
# sigstore-produced signatures and certificates.
128128
run: >-
129129
gh release upload
130-
'${{ github.ref_name }}' dist/**
130+
'v1.5.5' dist/**
131131
--repo '${{ github.repository }}'

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "AES_Python"
7-
version = "v1.5.4"
7+
version = "v1.5.5"
88
description = "AES (Advanced Encryption Standard) implementation in Python-3"
99
readme = "README.md"
1010
authors = [{name = "Gabriel Lindeblad", email = "Gabriel.Lindeblad@icloud.com"}]

src/AES_Python/main.py

Lines changed: 222 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from numpy.typing import NDArray # Used for type hinting numpy arrays.
1212
from typing import Any # Used for type hinting __getattr__ function.
1313
from 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

1618
class 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')

temp/test.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
import numpy as np
2+
import os
23
from AES_Python import AES
34

4-
aes_test = AES(key="8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b")
5+
aes_test = AES(running_mode="CBC",
6+
key="2b7e151628aed2a6abf7158809cf4f3c",
7+
iv="000102030405060708090a0b0c0d0e0f"
8+
)
59

6-
print(aes_test, "\n")
10+
data = b'\x1b\x16\x86:\xb9*w\xc5)"\xe4\xe9D\\\xf1\xee\x8b\x03\xcc\xe7\x0c~\xba7\xcf\x0f\x9c\x16dM$\xe9\x91\xef\xc3\xa6\xd2\xf0\xcd\xc2\xee\x86\xf0\x90\x8a]\x87\xf5R\xe2.c\xd4\xc6T\xdc\xe0#\xa7X\x8b_\x81\x04'
11+
file_name = "tmp.txt"
12+
expected = b'1234567890123456789012345678901234567890'
713

8-
data = '1234567890123456'
14+
with open(f"{file_name}.enc", "wb") as file:
15+
file.write(data)
916

10-
print("Original data:", data)
17+
aes_test.dec(file_path=f"{file_name}.enc")
1118

12-
enc_data = aes_test.enc(data_string=data)
19+
with open(f"{file_name}", "rb") as file:
20+
result = file.read()
1321

14-
print("Encrypted data:", enc_data)
22+
os.remove(f"{file_name}")
1523

16-
dec_data = aes_test.dec(data_string=enc_data)
17-
18-
print("Decrypted data:", dec_data)
24+
print("Result:", result)
25+
print("Expected:", expected)

0 commit comments

Comments
 (0)