From be35b108f9138e0c5ae0ce473d0cedd28c3eea20 Mon Sep 17 00:00:00 2001 From: Gabriel Lindeblad Date: Thu, 25 Apr 2024 12:51:43 +0200 Subject: [PATCH 1/4] Fixed check in main encryption functions, and implemented CBC string encryption. Signed-off-by: Gabriel Lindeblad --- .github/workflows/publish.yml | 4 +- pyproject.toml | 2 +- src/AES_Python/main.py | 76 +++++++++++++++++++++++++++++++---- temp/test.py | 4 +- 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 099f73e..a05895c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -116,7 +116,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.RELEASE }} run: >- gh release create - 'v1.5.4' + 'v1.5.5' --repo '${{ github.repository }}' --generate-notes - name: Upload artifact signatures to GitHub Release @@ -127,5 +127,5 @@ jobs: # sigstore-produced signatures and certificates. run: >- gh release upload - '${{ github.ref_name }}' dist/** + 'v1.5.5' dist/** --repo '${{ github.repository }}' \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 29a5bef..598a3df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "AES_Python" -version = "v1.5.4" +version = "v1.5.5" description = "AES (Advanced Encryption Standard) implementation in Python-3" readme = "README.md" authors = [{name = "Gabriel Lindeblad", email = "Gabriel.Lindeblad@icloud.com"}] diff --git a/src/AES_Python/main.py b/src/AES_Python/main.py index 4542dd6..1962efc 100644 --- a/src/AES_Python/main.py +++ b/src/AES_Python/main.py @@ -157,17 +157,17 @@ def enc(self, *, data_string: str = "", file_path: str = "", if not running_mode: running_mode = self._running_mode else: - self._running_mode = running_mode + self.set("running_mode", running_mode) if not key: key = self._key else: - self._key = key + self.set("key", key) if not iv: iv = self._iv else: - self._iv = iv + self.set("iv", iv) if data_string: if running_mode == "ECB": @@ -193,17 +193,17 @@ def dec(self, *, data_string: str = "", file_path: str = "", if not running_mode: running_mode = self._running_mode else: - self._running_mode = running_mode + self.set("running_mode", running_mode) if not key: key = self._key else: - self._key = key + self.set("key", key) if not iv: iv = self._iv else: - self._iv = iv + self.set("iv", iv) if data_string: if running_mode == "ECB": @@ -273,11 +273,71 @@ def __ecb_dec(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[ @classmethod def __cbc_enc(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[np.uint8], iv: str) -> str: - raise NotImplementedError("CBC encryption not yet implemented...") + """ + Preforms CBC encryption instructions on specified file or data string. + :param data_string: Data string to be encrypted. + :param file_path: Path to file that is encrypted. + :param keys: Key used for encryption. + :param iv: Initialization vector used. + :return: Data string or writes encrypted file. + """ + enc_array: NDArray[np.uint8] = np.frombuffer(bytes.fromhex(iv), dtype=np.uint8).reshape(4, 4) + + if data_string: + output_string: str = "" + + for i in range(len(data_string) // 16): # Encryption cycle, skips last if not integer multiple of 16 bytes + 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 = np.bitwise_xor(raw, enc_array) # Xor operation with previous encrypted block or iv + enc_array = cls.__enc_schedule(enc, keys) + output_string += "".join(cls.vec_chr(enc_array.flatten().astype(np.uint8))) + + extra = len(data_string) % 16 # Calculates length of final data block + result: str = "" + + if extra != 0: # If last data block not integer multiple of 16 adds extra padding + raw = np.full(16, 0, dtype=np.uint8) + raw[:extra] = np.array([ord(i) for i in data_string][-1 * extra:], dtype=np.uint8) + raw = raw.reshape(4, 4) + + temp_array = np.bitwise_xor(raw, enc_array) # Xor operation with previous encrypted block + + result = "".join(cls.vec_chr(cls.__enc_schedule(temp_array, keys).flatten().astype(np.uint8))) + + return output_string + result + else: + raise NotImplementedError @classmethod def __cbc_dec(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[np.uint8], iv: str) -> str: - raise NotImplementedError("CBC decryption not yet implemented...") + """ + Preforms CBC decryption instructions on specified file or data string. + :param data_string: Data string to be decrypted. + :param file_path: Path to file that is decrypted. + :param keys: Key used for decryption. + :param iv: Initialization vector used. + :return: Data string or writes decrypted file. + """ + dec_array: NDArray[np.uint8] = np.frombuffer(bytes.fromhex(iv), dtype=np.uint8).reshape(4, 4) + + if data_string: + output_string: str = "" + + for i in range(len(data_string) // 16): # Decryption cycle + raw: NDArray[np.uint8] = np.array( # Reads in input string 16 bytes at a time + [ord(i) for i in data_string[(i * 16): ((i + 1) * 16)]], dtype=np.uint8).reshape(4, 4) + + temp_array = cls.__dec_schedule(raw, keys) + result = np.bitwise_xor(temp_array, dec_array) + dec_array = raw + + dec = "".join(cls.vec_chr(result.flatten().astype(np.uint8))) + output_string += dec + + return output_string + else: + raise NotImplementedError @staticmethod def key_gen(length: int = 16) -> str: diff --git a/temp/test.py b/temp/test.py index 2aba116..c1a0f4c 100644 --- a/temp/test.py +++ b/temp/test.py @@ -1,11 +1,11 @@ import numpy as np from AES_Python import AES -aes_test = AES(key="8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b") +aes_test = AES(running_mode="CBC") print(aes_test, "\n") -data = '1234567890123456' +data = 'En lång text med en massa tecken som behöver hantaeras separat och kräver flera rundor' print("Original data:", data) From afe29fb4ef97a6549bef83247132c69ad60252f1 Mon Sep 17 00:00:00 2001 From: Gabriel Lindeblad Date: Thu, 25 Apr 2024 13:36:18 +0200 Subject: [PATCH 2/4] Setup and implemented CBC string encryption tests. Signed-off-by: Gabriel Lindeblad --- temp/test.py | 13 ++++++++++--- tests/test_dec.py | 39 +++++++++++++++++++++++++++++++++++++++ tests/test_enc.py | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/temp/test.py b/temp/test.py index c1a0f4c..e945809 100644 --- a/temp/test.py +++ b/temp/test.py @@ -1,17 +1,24 @@ import numpy as np from AES_Python import AES -aes_test = AES(running_mode="CBC") +aes_test = AES(running_mode="CBC", + key="2b7e151628aed2a6abf7158809cf4f3c", + iv="000102030405060708090A0B0C0D0E0F" + ) print(aes_test, "\n") -data = 'En lång text med en massa tecken som behöver hantaeras separat och kräver flera rundor' +data_raw = '6bc1bee22e409f96e93d7e117393172a' + +data = "".join([chr(i) for i in bytes.fromhex(data_raw)]) print("Original data:", data) enc_data = aes_test.enc(data_string=data) -print("Encrypted data:", enc_data) +print("Encrypted data:", bytearray([ord(i) for i in enc_data]).hex()) + +print("Expected:", "7649abac8119b246cee98e9b12e9197d") dec_data = aes_test.dec(data_string=enc_data) diff --git a/tests/test_dec.py b/tests/test_dec.py index 0b3c1fc..14d624e 100644 --- a/tests/test_dec.py +++ b/tests/test_dec.py @@ -66,3 +66,42 @@ def test_enc_ecb(data, key, expected): aes = AES(key=key) assert aes.dec(data_string=data.decode("utf-8")) == expected + + +@pytest.mark.parametrize("data,key,iv,expected", [ + # 128 bit + ('7649abac8119b246cee98e9b12e9197d', "2b7e151628aed2a6abf7158809cf4f3c", + "000102030405060708090a0b0c0d0e0f", "6bc1bee22e409f96e93d7e117393172a"), + ("5086cb9b507219ee95db113a917678b2", "2b7e151628aed2a6abf7158809cf4f3c", + "7649ABAC8119B246CEE98E9B12E9197D", 'ae2d8a571e03ac9c9eb76fac45af8e51'), + ("73bed6b8e3c1743b7116e69e22229516", "2b7e151628aed2a6abf7158809cf4f3c", + "5086CB9B507219EE95DB113A917678B2", '30c81c46a35ce411e5fbc1191a0a52ef'), + ("3ff1caa1681fac09120eca307586e1a7", "2b7e151628aed2a6abf7158809cf4f3c", + "73BED6B8E3C1743B7116E69E22229516", 'f69f2445df4f9b17ad2b417be66c3710'), + # 192 bit + ("4f021db243bc633d7178183a9fa071e8", "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "000102030405060708090A0B0C0D0E0F", '6bc1bee22e409f96e93d7e117393172a'), + ("b4d9ada9ad7dedf4e5e738763f69145a", "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "4F021DB243BC633D7178183A9FA071E8", 'ae2d8a571e03ac9c9eb76fac45af8e51'), + ("571b242012fb7ae07fa9baac3df102e0", "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "B4D9ADA9AD7DEDF4E5E738763F69145A", '30c81c46a35ce411e5fbc1191a0a52ef'), + ("08b0e27988598881d920a9e64f5615cd", "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "571B242012FB7AE07FA9BAAC3DF102E0", 'f69f2445df4f9b17ad2b417be66c3710'), + # 256 bit + ("f58c4c04d6e5f1ba779eabfb5f7bfbd6", "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "000102030405060708090A0B0C0D0E0F", '6bc1bee22e409f96e93d7e117393172a'), + ("9cfc4e967edb808d679f777bc6702c7d", "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "F58C4C04D6E5F1BA779EABFB5F7BFBD6", 'ae2d8a571e03ac9c9eb76fac45af8e51'), + ("39f23369a9d9bacfa530e26304231461", "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "9CFC4E967EDB808D679F777BC6702C7D", '30c81c46a35ce411e5fbc1191a0a52ef'), + ("b2eb05e2c39be9fcda6c19078c6a9d1b", "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "39F23369A9D9BACFA530E26304231461", 'f69f2445df4f9b17ad2b417be66c3710'), +]) +def test_dec_cbc(data, key, iv, expected): + aes = AES(running_mode="CBC", key=key, iv=iv) + + data = "".join([chr(i) for i in bytes.fromhex(data)]) + + enc_data = aes.dec(data_string=data) + + assert bytearray([ord(i) for i in enc_data]).hex() == expected diff --git a/tests/test_enc.py b/tests/test_enc.py index 8ffbd3c..ba47760 100644 --- a/tests/test_enc.py +++ b/tests/test_enc.py @@ -66,3 +66,42 @@ def test_enc_ecb(data, key, expected): aes = AES(key=key) assert bytes(aes.enc(data_string=data), "utf-8") == expected + + +@pytest.mark.parametrize("data,key,iv,expected", [ + # 128 bit + ('6bc1bee22e409f96e93d7e117393172a', "2b7e151628aed2a6abf7158809cf4f3c", + "000102030405060708090a0b0c0d0e0f", "7649abac8119b246cee98e9b12e9197d"), + ('ae2d8a571e03ac9c9eb76fac45af8e51', "2b7e151628aed2a6abf7158809cf4f3c", + "7649ABAC8119B246CEE98E9B12E9197D", "5086cb9b507219ee95db113a917678b2"), + ('30c81c46a35ce411e5fbc1191a0a52ef', "2b7e151628aed2a6abf7158809cf4f3c", + "5086CB9B507219EE95DB113A917678B2", "73bed6b8e3c1743b7116e69e22229516"), + ('f69f2445df4f9b17ad2b417be66c3710', "2b7e151628aed2a6abf7158809cf4f3c", + "73BED6B8E3C1743B7116E69E22229516", "3ff1caa1681fac09120eca307586e1a7"), + # 192 bit + ('6bc1bee22e409f96e93d7e117393172a', "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "000102030405060708090A0B0C0D0E0F", "4f021db243bc633d7178183a9fa071e8"), + ('ae2d8a571e03ac9c9eb76fac45af8e51', "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "4F021DB243BC633D7178183A9FA071E8", "b4d9ada9ad7dedf4e5e738763f69145a"), + ('30c81c46a35ce411e5fbc1191a0a52ef', "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "B4D9ADA9AD7DEDF4E5E738763F69145A", "571b242012fb7ae07fa9baac3df102e0"), + ('f69f2445df4f9b17ad2b417be66c3710', "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "571B242012FB7AE07FA9BAAC3DF102E0", "08b0e27988598881d920a9e64f5615cd"), + # 256 bit + ('6bc1bee22e409f96e93d7e117393172a', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "000102030405060708090A0B0C0D0E0F", "f58c4c04d6e5f1ba779eabfb5f7bfbd6"), + ('ae2d8a571e03ac9c9eb76fac45af8e51', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "F58C4C04D6E5F1BA779EABFB5F7BFBD6", "9cfc4e967edb808d679f777bc6702c7d"), + ('30c81c46a35ce411e5fbc1191a0a52ef', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "9CFC4E967EDB808D679F777BC6702C7D", "39f23369a9d9bacfa530e26304231461"), + ('f69f2445df4f9b17ad2b417be66c3710', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "39F23369A9D9BACFA530E26304231461", "b2eb05e2c39be9fcda6c19078c6a9d1b"), +]) +def test_enc_cbc(data, key, iv, expected): + aes = AES(running_mode="CBC", key=key, iv=iv) + + data = "".join([chr(i) for i in bytes.fromhex(data)]) + + enc_data = aes.enc(data_string=data) + + assert bytearray([ord(i) for i in enc_data]).hex() == expected From bd38a5dee76fb2fae0f83b3e365148cf65bb6e63 Mon Sep 17 00:00:00 2001 From: Gabriel Lindeblad Date: Thu, 25 Apr 2024 14:52:28 +0200 Subject: [PATCH 3/4] Implemented CBC and ECB file encryption and corrected tests to work for file encryption as well. Signed-off-by: Gabriel Lindeblad --- src/AES_Python/main.py | 169 ++++++++++++++++++++++++++++++++++++++--- temp/test.py | 26 +++---- tests/test_file_dec.py | 8 +- tests/test_file_enc.py | 2 - 4 files changed, 175 insertions(+), 30 deletions(-) diff --git a/src/AES_Python/main.py b/src/AES_Python/main.py index 1962efc..dd27361 100644 --- a/src/AES_Python/main.py +++ b/src/AES_Python/main.py @@ -4,6 +4,7 @@ be used in any other use case than educational and no security is guaranteed for data encrypted or decrypted using this tool. """ +import os # Imported libraries import numpy as np # Used for arrays and mathematical operations. @@ -11,6 +12,8 @@ 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. +from os.path import getsize # Used to acquire size of files. +from os import remove # Used to remove files. class AES: @@ -138,7 +141,7 @@ def get(self, item: str) -> Any: :param item: Attribute to be retrieved. Valid attributes (running_mode, key, iv). :return: Attribute value. """ - if item: + if item in ["running_mode", "key", "iv"]: return self.__dict__[f"_{item}"] else: raise AttributeError(f"No attribute <{item}> exists!") @@ -176,8 +179,17 @@ def enc(self, *, data_string: str = "", file_path: str = "", return self.__cbc_enc(data_string=data_string, keys=self.key_expand(key), iv=iv) else: raise NotImplementedError(f"{running_mode} is not supported!") + elif file_path: + if running_mode == "ECB": + self.__ecb_enc(file_path=file_path, keys=self.key_expand(key)) + return "" + elif running_mode == "CBC": + self.__cbc_enc(file_path=file_path, keys=self.key_expand(key), iv=iv) + return "" + else: + raise NotImplementedError(f"{running_mode} is not supported!") else: - raise NotImplementedError("File encryption is not implemented yet...") + raise RuntimeWarning("No file or string was give...") def dec(self, *, data_string: str = "", file_path: str = "", running_mode: str = "", key: str = "", iv: str = "") -> str: @@ -212,8 +224,17 @@ def dec(self, *, data_string: str = "", file_path: str = "", return self.__cbc_dec(data_string=data_string, keys=self.key_expand(key), iv=iv) else: raise NotImplementedError(f"{running_mode} is not supported!") + elif file_path: + if running_mode == "ECB": + self.__ecb_dec(file_path=file_path, keys=self.key_expand(key)) + return "" + elif running_mode == "CBC": + self.__cbc_dec(file_path=file_path, keys=self.key_expand(key), iv=iv) + return "" + else: + raise NotImplementedError(f"{running_mode} is not supported!") else: - raise NotImplementedError("File encryption is not implemented yet...") + raise RuntimeWarning("No file or string was give...") @classmethod def __ecb_enc(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[np.uint8]) -> str: @@ -234,7 +255,7 @@ def __ecb_enc(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[ enc: str = "".join(cls.vec_chr(cls.__enc_schedule(raw, keys).flatten().astype(np.uint8))) output_string += enc - extra = len(data_string) % 16 # Calculates length of final data block + extra = len(data_string) % 16 # Calculates length of final data block result: str = "" if extra != 0: # If last data block not integer multiple of 16 adds extra padding @@ -244,8 +265,33 @@ def __ecb_enc(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[ result = "".join(cls.vec_chr(cls.__enc_schedule(raw.reshape(4, 4), keys).flatten().astype(np.uint8))) return output_string + result + elif file_path: + file_size = getsize(file_path) + + with open(f"{file_path}.enc", 'wb') as output, open(file_path, 'rb') as data: + for i in range(int(file_size / 16)): + raw = np.array([i for i in data.read(16)], dtype=np.uint8).reshape(4, 4) + out = bytes((cls.__enc_schedule(raw, keys).flatten()).tolist()) + output.write(out) + + if file_size % 16 != 0: + raw_l = [i for i in data.read()] + raw_l, length = cls.__add_padding(raw_l) + + out = bytes((cls.__enc_schedule(np.array(raw_l).reshape(4, 4), keys).flatten()).tolist()) + identifier = bytes((cls.__enc_schedule(np.array([0 for i in range(15)] + [length]).reshape(4, 4), + keys).flatten()).tolist()) + + output.write(out + identifier) + else: + identifier = bytes((cls.__enc_schedule(np.array([0 for i in range(16)]).reshape(4, 4), + keys).flatten()).tolist()) + output.write(identifier) + + remove(file_path) + return "" else: - raise NotImplementedError + raise RuntimeError("No string or file path received...?") @classmethod def __ecb_dec(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[np.uint8]) -> str: @@ -268,8 +314,30 @@ def __ecb_dec(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[ output_string += dec return output_string + elif file_path: + file_size = getsize(file_path) + file_name = file_path[:-4] + + with open(f"{file_name}", 'wb') as output, open(file_path, 'rb') as data: + for i in range(int(file_size / 16) - 2): + raw = np.array([i for i in data.read(16)]).reshape(4, 4) + result = bytes((cls.__dec_schedule(raw, keys).flatten()).tolist()) + output.write(result) + + data_final = np.array([i for i in data.read(16)]).reshape(4, 4) + identifier = np.array([i for i in data.read()]).reshape(4, 4) + + data_dec = (cls.__dec_schedule(data_final, keys).flatten()).tolist() + id_l = (cls.__dec_schedule(identifier, keys).flatten()).tolist() + + result = bytes(cls.__remove_padding(data_dec, id_l)) + + output.write(result) + + remove(file_path) + return "" else: - raise NotImplementedError + raise RuntimeError("No string or file path received...?") @classmethod def __cbc_enc(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[np.uint8], iv: str) -> str: @@ -289,7 +357,7 @@ def __cbc_enc(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[ for i in range(len(data_string) // 16): # Encryption cycle, skips last if not integer multiple of 16 bytes 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 = np.bitwise_xor(raw, enc_array) # Xor operation with previous encrypted block or iv + enc = np.bitwise_xor(raw, enc_array) # Xor operation with previous encrypted block or iv enc_array = cls.__enc_schedule(enc, keys) output_string += "".join(cls.vec_chr(enc_array.flatten().astype(np.uint8))) @@ -306,8 +374,35 @@ def __cbc_enc(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[ result = "".join(cls.vec_chr(cls.__enc_schedule(temp_array, keys).flatten().astype(np.uint8))) return output_string + result + elif file_path: + file_size = getsize(file_path) + + with open(f"{file_path}.enc", 'wb') as output, open(file_path, 'rb') as data: + for i in range(int(file_size / 16)): + raw = np.array([i for i in data.read(16)]).reshape(4, 4) + raw = np.bitwise_xor(raw, enc_array) + enc_array = cls.__enc_schedule(raw, keys) + output.write(bytes((enc_array.flatten()).tolist())) + + if file_size % 16 != 0: + final = [i for i in data.read()] + final, length = cls.__add_padding(final) + + raw = np.bitwise_xor(np.array(final).reshape(4, 4), enc_array) + enc_array = cls.__enc_schedule(raw, keys) + + identifier = np.bitwise_xor(np.array([0 for i in range(15)] + [length]).reshape(4, 4), enc_array) + identifier = cls.__enc_schedule(identifier, keys) + + output.write(bytes((enc_array.flatten()).tolist() + (identifier.flatten()).tolist())) + else: + identifier = np.bitwise_xor(np.array([0 for i in range(16)]).reshape(4, 4), enc_array) + id_bytes = bytes(((cls.__enc_schedule(identifier, keys)).flatten()).tolist()) + output.write(id_bytes) + remove(file_path) + return "" else: - raise NotImplementedError + raise RuntimeError("No string or file path received...?") @classmethod def __cbc_dec(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[np.uint8], iv: str) -> str: @@ -325,7 +420,7 @@ def __cbc_dec(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[ output_string: str = "" for i in range(len(data_string) // 16): # Decryption cycle - raw: NDArray[np.uint8] = np.array( # Reads in input string 16 bytes at a time + raw: NDArray[np.uint8] = np.array( # Reads in input string 16 bytes at a time [ord(i) for i in data_string[(i * 16): ((i + 1) * 16)]], dtype=np.uint8).reshape(4, 4) temp_array = cls.__dec_schedule(raw, keys) @@ -336,8 +431,44 @@ def __cbc_dec(cls, *, data_string: str = "", file_path: str = "", keys: NDArray[ output_string += dec return output_string + elif file_path: + file_size = getsize(file_path) + file_name = file_path[:-4] + + with open(f"{file_name}", 'wb') as output, open(file_path, 'rb') as data: + if int(file_size / 16) - 3 >= 0: + vector = np.array([i for i in data.read(16)]).reshape(4, 4) + raw = cls.__dec_schedule(vector, keys) + result = np.bitwise_xor(raw, dec_array) + output.write(bytes((result.flatten()).tolist())) + + for i in range(int(file_size / 16) - 3): + raw = np.array([i for i in data.read(16)]).reshape(4, 4) + result = cls.__dec_schedule(raw, keys) + result = np.bitwise_xor(result, vector) + vector = raw + output.write(bytes((result.flatten()).tolist())) + else: + vector = dec_array + + data_pice = np.array([i for i in data.read(16)]).reshape(4, 4) + vector_1, identifier = data_pice, np.array([i for i in data.read()]).reshape(4, 4) + + result = cls.__dec_schedule(data_pice, keys) + identifier = cls.__dec_schedule(identifier, keys) + + identifier = np.bitwise_xor(identifier, vector_1) + data_pice = np.bitwise_xor(result, vector) + + result_bytes = bytes(cls.__remove_padding((data_pice.flatten()).tolist(), + (identifier.flatten()).tolist())) + + output.write(result_bytes) + + remove(file_path) + return "" else: - raise NotImplementedError + raise RuntimeError("No string or file path received...?") @staticmethod def key_gen(length: int = 16) -> str: @@ -533,3 +664,21 @@ def __mix_columns(matrix: NDArray[np.uint8], shift: int) -> NDArray[np.uint8]: result[j, i] = temp return result.transpose() + + @staticmethod + def __add_padding(data: list[int]) -> tuple[list[int], int]: + """Adds a padding to ensure a bloke size of 16 bytes.""" + length = 16 - len(data) + for i in range(length): + data.append(0) + return data, length + + @staticmethod + def __remove_padding(data: list[int], identifier: list[int]) -> list[int]: + """Removes the padding from the data using information from identifier.""" + if identifier[-1] == 0: + return data + elif 0 < identifier[-1] < 16: + return data[:-identifier[-1]] + else: + raise ValueError('Invalid padding') diff --git a/temp/test.py b/temp/test.py index e945809..39d9a64 100644 --- a/temp/test.py +++ b/temp/test.py @@ -1,25 +1,25 @@ import numpy as np +import os from AES_Python import AES aes_test = AES(running_mode="CBC", key="2b7e151628aed2a6abf7158809cf4f3c", - iv="000102030405060708090A0B0C0D0E0F" + iv="000102030405060708090a0b0c0d0e0f" ) -print(aes_test, "\n") +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' +file_name = "tmp.txt" +expected = b'1234567890123456789012345678901234567890' -data_raw = '6bc1bee22e409f96e93d7e117393172a' +with open(f"{file_name}.enc", "wb") as file: + file.write(data) -data = "".join([chr(i) for i in bytes.fromhex(data_raw)]) +aes_test.dec(file_path=f"{file_name}.enc") -print("Original data:", data) +with open(f"{file_name}", "rb") as file: + result = file.read() -enc_data = aes_test.enc(data_string=data) +os.remove(f"{file_name}") -print("Encrypted data:", bytearray([ord(i) for i in enc_data]).hex()) - -print("Expected:", "7649abac8119b246cee98e9b12e9197d") - -dec_data = aes_test.dec(data_string=enc_data) - -print("Decrypted data:", dec_data) +print("Result:", result) +print("Expected:", expected) \ No newline at end of file diff --git a/tests/test_file_dec.py b/tests/test_file_dec.py index a1c1ec7..3456b9f 100644 --- a/tests/test_file_dec.py +++ b/tests/test_file_dec.py @@ -3,7 +3,6 @@ from AES_Python import AES -@pytest.mark.skip(reason="Not correctly implemented yet") @pytest.mark.parametrize("data,key,file_name,expected", [ # 128 bit (b'|\x94\x18\xcf\x1c\xf0\xef\xa0\xff\xa4\xbb\xe9\xd8\x8am\xa40f\xe4\x1eg\x9d\x88\xb8\xef\xeb{=J\xf3\xf6\xc1', @@ -28,7 +27,7 @@ def test_file_dec_ecb(data, key, file_name, expected): file.write(data) ecb = AES(key=key, running_mode="ECB") - ecb.dec(file_path=file_name) + ecb.dec(file_path=f"{file_name}.enc") with open(file_name, "rb") as file: result = file.read() @@ -38,7 +37,6 @@ def test_file_dec_ecb(data, key, file_name, expected): assert result == expected -@pytest.mark.skip(reason="Not correctly implemented yet") @pytest.mark.parametrize("data,key,file_name,iv,expected", [ # 128 bit (b'\xe4\xa7\x0e\xbd\x84\xfa\xf5\xd8`\xb8\xa1\x10\x0b~\xadh\x89Feso\xc5~_|\xe9\x1bG\xd9*\\\x81', @@ -64,8 +62,8 @@ def test_file_dec_cbc(data, key, file_name, iv, expected): with open(f"{file_name}.enc", "wb") as file: file.write(data) - cbc = AES(key=key, running_mode="CBC") - cbc.dec(file_path=file_name) + cbc = AES(key=key, iv=iv, running_mode="CBC") + cbc.dec(file_path=f"{file_name}.enc") with open(file_name, "rb") as file: result = file.read() diff --git a/tests/test_file_enc.py b/tests/test_file_enc.py index 3663f2b..96a74da 100644 --- a/tests/test_file_enc.py +++ b/tests/test_file_enc.py @@ -3,7 +3,6 @@ from AES_Python import AES -@pytest.mark.skip(reason="Not correctly implemented yet") @pytest.mark.parametrize("data,key,file_name,expected", [ # 128 bit (b'1234567890', "2b7e151628aed2a6abf7158809cf4f3c", "tmp.txt", @@ -36,7 +35,6 @@ def test_file_enc_ecb(data, key, file_name, expected): assert result == expected -@pytest.mark.skip(reason="Not correctly implemented yet") @pytest.mark.parametrize("data,key,file_name,iv,expected", [ # 128 bit (b'1234567890', "2b7e151628aed2a6abf7158809cf4f3c", "tmp.txt", From 6fe925ef659b340340b6b55f74fd879428964d93 Mon Sep 17 00:00:00 2001 From: Gabriel Lindeblad Date: Thu, 25 Apr 2024 14:55:38 +0200 Subject: [PATCH 4/4] Removed unused import. Signed-off-by: Gabriel Lindeblad --- src/AES_Python/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AES_Python/main.py b/src/AES_Python/main.py index dd27361..d52e578 100644 --- a/src/AES_Python/main.py +++ b/src/AES_Python/main.py @@ -4,7 +4,6 @@ be used in any other use case than educational and no security is guaranteed for data encrypted or decrypted using this tool. """ -import os # Imported libraries import numpy as np # Used for arrays and mathematical operations.