Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #68

Closed
wants to merge 2 commits into from
Closed

Dev #68

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name: Test
on:
pull_request:
branches: [core]
push:
branches: [dev]

permissions:
contents: read
Expand Down
156 changes: 124 additions & 32 deletions src/AES_Python/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import numpy as np # Used for arrays and mathematical operations.
import galois # Used for GF(2^8) multiplication in mix columns operation.
from numpy.typing import NDArray # Used for type hinting numpy arrays.
from typing import Any # Used for type hinting __getattr__ function.
from secrets import token_bytes # Used for generating random key if needed.


Expand All @@ -16,16 +17,14 @@ class AES:
It supports different modes of operation (ECB, CBC) and key lengths (128, 256, 512 bits).

Attributes:
version (int): The version of the encryption, either 128, 192 or 256 bit.
r_mode (str): The running mode for AES. Default is "ECB".
version (str): The version of the encryption, either 128, 192 or 256 bit.
running_mode (str): The running mode for AES. Default is "ECB".
key (str): The encryption key. If not provided, a random key is generated.

Methods:
set_key: Sets the encryption key.
get_key: Returns the current encryption key.
enc: Encrypts string of unspecified length.
dec: Decrypts string.
key_gen: Generates a random byte string of specified length (16, 24 or 32 bytes).
enc: Encrypts either a string of unspecified length or a file.
dec: Decrypts string or file.
key_gen: Generates a random byte string of specified length (16, 24 or 32 bytes) in hexadecimal.
key_expand: Expands the given key to 11, 13 or 15 round keys depending on key length.
"""

Expand Down Expand Up @@ -69,6 +68,9 @@ class AES:
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
])

# Vectorize built-in chr() function
vec_chr = np.vectorize(chr)

def __init__(self, *, running_mode: str = "ECB", version: str = "128", key: str = "", iv: str = "") -> None:
"""
Initialization of AES object.
Expand All @@ -88,40 +90,130 @@ def __init__(self, *, running_mode: str = "ECB", version: str = "128", key: str
else:
self.iv = str(self.key_gen()) # Generates iv if missing

self.r_mode: str = running_mode
self.running_mode: str = running_mode

def set_key(self, key) -> None:
"""
Changes the current key used for encryption and decryption.
:param key: New key, must be either 16, 24 or 32 bytes.
:return: None.
"""
if isinstance(key, str) and ((len(key) / 2) in [16, 24, 32]):
self.key = key
def __setattr__(self, attr, value) -> None:
if attr == "key":
if isinstance(value, str) and ((len(value) / 2) in [16, 24, 32]):
self.__dict__[attr] = value
else:
raise TypeError("Unsupported key length, supported types are (16, 24, 32) bytes.")
elif attr == "iv":
if isinstance(value, str) and ((len(value) / 2) == 16):
self.__dict__[attr] = value
else:
raise TypeError("Unsupported iv length, supported length is 16 bytes.")
elif attr == "running_mode":
if isinstance(value, str) and value in ["ECB", "CBC"]:
self.__dict__[attr] = value
else:
raise TypeError("Unsupported running mode, supported modes are ECB, CBC.")
else:
raise TypeError("Unsupported key length, supported types are (16, 24, 32) bytes.")
raise AttributeError(f"No changeable attribute <{attr}> exists!")

def get_key(self) -> str:
"""
Gets the current key used for encryption and decryption.
:return: Key as string of hexadecimals.
"""
return self.key
def __getattr__(self, item) -> Any:
if item in ["key", "iv", "running_mode"]:
return self.__dict__[item]
else:
raise AttributeError(f"No attribute <{item}> exists!")

def __delattr__(self, item):
raise PermissionError(f"You can not delete the attribute <{item}>")

def __repr__(self) -> str:
return (f"Object <{type(self).__name__}>. \n"
f" - Running mode = '{self.running_mode}' \n"
f" - Key = '{self.key}' \n"
f" - IV = '{self.iv}' ")

def enc(self, *, data_string: str = "", file_path: str = "",
running_mode: str = "", key: str = "", iv: str = "") -> str | None:
raise NotImplementedError
if not running_mode:
running_mode = self.running_mode

if not key:
key = self.key

if not iv:
iv = self.iv

if data_string:
if running_mode == "ECB":
return self.__ecb_enc(data_string=data_string, keys=self.key_expand(key))
elif running_mode == "CBC":
raise NotImplementedError
else:
raise NotImplementedError
else:
raise NotImplementedError

def dec(self, *, data_string: str = "", file_path: str = "",
running_mode: str = "", key: str = "", iv: str = "") -> str | None:
raise NotImplementedError
if not running_mode:
running_mode = self.running_mode

def __ecb(self):
raise NotImplementedError
if not key:
key = self.key

def __cbc(self) -> str | None:
if not iv:
iv = self.iv

if data_string:
if running_mode == "ECB":
return self.__ecb_dec(data_string=data_string, keys=self.key_expand(key))
elif running_mode == "CBC":
raise NotImplementedError
else:
raise NotImplementedError
else:
raise NotImplementedError

def __ecb_enc(self, *, data_string: str = "", file_path: str = "", keys: NDArray[np.uint8]) -> str | None:
if data_string:
output_string: str = ""

for i in range(len(data_string) // 16):
raw: NDArray[np.uint8] = np.array([ord(i) for i in data_string[(i * 16): ((i + 1) * 16)]],
dtype=np.uint8).reshape(4, 4)

enc: str = "".join(self.vec_chr(self.__enc_schedule(raw, keys).flatten().astype(np.uint8)))
output_string += enc

extra = len(data_string) % 16
result: str = ""

if extra != 0:
raw = np.full(16, 0, dtype=np.uint8)
raw[:extra] = np.array([ord(i) for i in data_string][-1 * extra:], dtype=np.uint8)

result = "".join(self.vec_chr(self.__enc_schedule(raw.reshape(4, 4), keys).flatten().astype(np.uint8)))

return output_string + result
else:
raise NotImplementedError

def __ecb_dec(self, *, data_string: str = "", file_path: str = "", keys: NDArray[np.uint8]) -> str | None:
if data_string:
output_string: str = ""

for i in range(len(data_string) // 16):
raw: NDArray[np.uint8] = np.array(
[ord(i) for i in data_string[(i * 16): ((i + 1) * 16)]], dtype=np.uint8).reshape(4, 4)

dec: str = "".join(self.vec_chr(self.__dec_schedule(raw, keys).flatten().astype(np.uint8)))

output_string += dec

return output_string
else:
raise NotImplementedError

def __cbc_enc(self, *, data_string: str = "", file_path: str = "", keys: NDArray[np.uint8]) -> str | None:
raise NotImplementedError

def __cbc_dec(self, *, data_string: str = "", file_path: str = "", keys: NDArray[np.uint8]) -> str | None:
raise NotImplementedError

@staticmethod
def key_gen(length: int = 16) -> str:
""" Generates a random byte string of specified length using secrets library.
Expand Down Expand Up @@ -239,10 +331,10 @@ def __key_schedule(cls, key: NDArray[np.uint8], nr: int, nc: int) -> NDArray[np.
[0xab, 0x00, 0x00, 0x00],
[0x4d, 0x00, 0x00, 0x00],
[0x9a, 0x00, 0x00, 0x00],
])
], dtype=np.uint8)

# Setup list of matrices to store the words
words: NDArray[np.uint8] = np.full((nr * 4, 4), 0, dtype=int)
words: NDArray[np.uint8] = np.full((nr * 4, 4), 0, dtype=np.uint8)

# Populating first words with key
words[0:nc] = np.array_split(key, nc)
Expand Down Expand Up @@ -294,11 +386,11 @@ def __mix_columns(matrix: NDArray[np.uint8], shift: int) -> NDArray[np.uint8]:
cx: NDArray[np.uint8] = np.array([[2, 3, 1, 1], # Matrix used for shift columns operation
[1, 2, 3, 1],
[1, 1, 2, 3],
[3, 1, 1, 2]])
[3, 1, 1, 2]], dtype=np.uint8)
dx: NDArray[np.uint8] = np.array([[14, 11, 13, 9], # Matrix used for inverse shift columns operation
[9, 14, 11, 13],
[13, 9, 14, 11],
[11, 13, 9, 14]])
[11, 13, 9, 14]], dtype=np.uint8)

# Determines if preforming inverse operation or not
if shift < 0:
Expand Down
23 changes: 9 additions & 14 deletions temp/test.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import numpy as np
import sys
sys.path.append('../')
from AES_Python import AES

A = "6bc1bee22e409f96e93d7e117393172a"
aes_test = AES(key="8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b")

print(A)
print(aes_test)

B = np.frombuffer(bytes.fromhex(A), dtype=np.uint8).reshape(4, 4)
data = '1234567890123456'

print(B)
enc_data = aes_test.enc(data_string=data)

print(B.astype(np.int8).tobytes().hex())
print("Enc_data:", enc_data)
print("Bytes_data:", bytes(enc_data, "utf-8")) # type:ignore

C = np.array([[2, 3, 1, 1], # Matrix used for shift columns operation
[1, 2, 3, 1],
[1, 1, 2, 3],
[3, 1, 1, 2]])
dec_data = aes_test.dec(data_string=enc_data) # type: ignore

print(C)
C.transpose()
print(C)
print(dec_data)
23 changes: 23 additions & 0 deletions tests/test_enc.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,26 @@ def test_enc_schedule(data, key: str, expected: str) -> None:

# Evaluates result
assert result_formatted == expected


@pytest.mark.parametrize("data,key,expected", [
# 128 bit
('1234567890', "2b7e151628aed2a6abf7158809cf4f3c",
b'|\xc2\x94\x18\xc3\x8f\x1c\xc3\xb0\xc3\xaf\xc2\xa0\xc3\xbf\xc2\xa4\xc2\xbb\xc3\xa9\xc3\x98\xc2\x8am\xc2\xa4'),
('1234567890123456', "2b7e151628aed2a6abf7158809cf4f3c",
b'(>\xc2\xa4JH\xc3\x97\x18\xc2\xa2\xc3\x81\xc3\xb7\xc2\xb7\xc3\xa3\xc2\xbbKJ\xc3\xb8'),
# 192 bit
('1234567890', "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
b'K\xc2\x9d\xc3\xa5\xc3\xa9l8&\xc3\x9alO\xc2\xbb\xc3\x83\xc3\xb2\xc3\x83*\xc3\xb2'),
('1234567890123456', "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
b'\xc3\xb9\x01\xc3\x97\xc3\xa8\xc3\x9c\xc3\xb7\\\xc3\x80\xc3\x88\xc2\xa1*>t\xc2\xabA\xc3\x98'),
# 256 bit
('1234567890', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
b'2 ?\xc3\xabm\xc3\xb5o\xc3\x82\xc2\x8b\xc2\x90\xc2\x80\xc2\x84 D\xc3\x84\xc2\x95'),
('1234567890123456', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
b"\xc2\x8cc'\xc3\x88d\xc2\x82\xc2\xb3\xc2\x8cj\xc3\x92\\\xc2\xaa\xc2\x96\xc3\xb1\xc3\xbfi")
])
def test_enc_ecb(data, key, expected):
aes = AES(key=key)

assert bytes(aes.enc(data_string=data), "utf-8") == expected # type: ignore
4 changes: 2 additions & 2 deletions tests/test_file_dec.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
(b"\x8cc'\xc8d\x82\xb3\x8cj\xd2\\\xaa\x96\xf1\xffi\xe5h\xf6\x81\x94\xcfv\xd6\x17ML\xc0C\x10\xa8T",
"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "tmp6.txt", b'1234567890123456')
])
def test_dec_ecb(data, key, file_name, expected):
def test_file_dec_ecb(data, key, file_name, expected):
with open(f"{file_name}.enc", "wb") as file:
file.write(data)

Expand Down Expand Up @@ -60,7 +60,7 @@ def test_dec_ecb(data, key, file_name, expected):
(b'a\xfdIRQ\xf8\xf1D\xcc\xbf\x89\xc8\xd6\xec\x01;pNAT\xedT\xd9Tp-_\xbbr\xd3\xb5\x11',
"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "tmp6.txt", "000102030405060708090a0b0c0d0e0f", b'1234567890123456')
])
def test_dec_cbc(data, key, file_name, iv, expected):
def test_file_dec_cbc(data, key, file_name, iv, expected):
with open(f"{file_name}.enc", "wb") as file:
file.write(data)

Expand Down
4 changes: 2 additions & 2 deletions tests/test_file_enc.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
(b'1234567890123456', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "tmp5.txt",
b"\x8cc'\xc8d\x82\xb3\x8cj\xd2\\\xaa\x96\xf1\xffi\xe5h\xf6\x81\x94\xcfv\xd6\x17ML\xc0C\x10\xa8T")
])
def test_enc_ecb(data, key, file_name, expected):
def test_file_enc_ecb(data, key, file_name, expected):
with open(file_name, "wb") as file:
file.write(data)

Expand Down Expand Up @@ -60,7 +60,7 @@ def test_enc_ecb(data, key, file_name, expected):
"000102030405060708090a0b0c0d0e0f",
b'a\xfdIRQ\xf8\xf1D\xcc\xbf\x89\xc8\xd6\xec\x01;pNAT\xedT\xd9Tp-_\xbbr\xd3\xb5\x11')
])
def test_enc_cbc(data, key, file_name, iv, expected):
def test_file_enc_cbc(data, key, file_name, iv, expected):
with open(file_name, "wb") as file:
file.write(data)

Expand Down
Loading