diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml new file mode 100644 index 0000000..b2d7c16 --- /dev/null +++ b/.github/workflows/core.yml @@ -0,0 +1,90 @@ +# This workflow will upload a Python Package using Twine when a push or pull-request pushed thru to the core branch. + +name: Core Build & Publish + +on: + merge: + branches: [core] + push: + branches: [core] + +permissions: + contents: read + +jobs: + validate: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.9', '3.10'] + + steps: + - uses: actions/checkout@v3 + - name: Set up python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: + python -m pip install --upgrade pip + pip install tox tox-gh-actions + - name: Test + run: tox + + build: + needs: + - validate + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Building package + run: python -m build + + release: + needs: + - build + - validate + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Create release + uses: 'marvinpinto/action-automatic-releases@latest' + with: + repo_token: ${{ secrets.RELEASE_TOKEN }} + automatic_release_tag: $(python setup.py --version) + prerelease: false + title: $(python setup.py --version) + files: | + dist/* + + deploy: + needs: + - release + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index 9d03d17..0000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,62 +0,0 @@ -# This workflow will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Build & Upload Python Package - -on: - release: - branches: [core] - types: [published] - -permissions: - contents: read - -jobs: - validate: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - python-version: ['3.9', '3.10'] - - steps: - - uses: actions/checkout@v2 - - name: Set up python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: - python -m pip install --upgrade pip - pip install tox tox-gh-actions - - name: Test with tox - run: tox - - deploy: - needs: - - validate - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1c5101..661eb36 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,13 @@ -name: Tests +name: Test on: - - push - - pull_request + push: + branches: [dev] + pull_request: + branches: [core] + +permissions: + contents: read jobs: test: diff --git a/README.md b/README.md index 3614696..64a2306 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AES-Python -![Tests](https://github.com/Circut-labs/AES-Python/actions/workflows/test.yml/badge.svg) [![Build & Upload Python Package](https://github.com/Circut-labs/AES-Python/actions/workflows/python-publish.yml/badge.svg)](https://github.com/Circut-labs/AES-Python/actions/workflows/python-publish.yml) +![Tests](https://github.com/Circut-labs/AES-Python/actions/workflows/test.yml/badge.svg) [![CodeQL](https://github.com/Circut-labs/AES-Python/actions/workflows/codeql-analysis.yml/badge.svg?branch=core)](https://github.com/Circut-labs/AES-Python/actions/workflows/codeql-analysis.yml) [![Build & Upload Python Package](https://github.com/Circut-labs/AES-Python/actions/workflows/python-publish.yml/badge.svg)](https://github.com/Circut-labs/AES-Python/actions/workflows/python-publish.yml) - [AES-Python](#aes-python) - [About](#about) @@ -19,6 +19,7 @@ Implemented running modes --- - [x] **ECB** - Electronic Code Book mode (For more information see ...) - [x] **CBC** - Cipher Block Chaining mode (For more information see ...) +- [x] **PCBC** - Propagating Cipher Block Chaining mode (For more information see ...) - [ ] **CFB** - Cipher Feedback mode (For more information see ...) - [ ] **OFB** - Output FeedBack mode (For more information see ...) - [ ] **CTR** - Counter mode (For more information see ...) diff --git a/dist/AES-Python-1.0.0.tar.gz b/dist/AES-Python-1.0.0.tar.gz deleted file mode 100644 index 5be24a3..0000000 Binary files a/dist/AES-Python-1.0.0.tar.gz and /dev/null differ diff --git a/dist/AES-Python-1.1.0.tar.gz b/dist/AES-Python-1.1.0.tar.gz new file mode 100644 index 0000000..33ec039 Binary files /dev/null and b/dist/AES-Python-1.1.0.tar.gz differ diff --git a/dist/AES_Python-1.0.0-py3-none-any.whl b/dist/AES_Python-1.0.0-py3-none-any.whl deleted file mode 100644 index 55dd444..0000000 Binary files a/dist/AES_Python-1.0.0-py3-none-any.whl and /dev/null differ diff --git a/dist/AES_Python-1.1.0-py3-none-any.whl b/dist/AES_Python-1.1.0-py3-none-any.whl new file mode 100644 index 0000000..b9ca89e Binary files /dev/null and b/dist/AES_Python-1.1.0-py3-none-any.whl differ diff --git a/index.md b/index.md new file mode 100644 index 0000000..7c64b3b --- /dev/null +++ b/index.md @@ -0,0 +1,19 @@ +# AES-Python + +![Tests](https://github.com/Circut-labs/AES-Python/actions/workflows/test.yml/badge.svg) [![CodeQL](https://github.com/Circut-labs/AES-Python/actions/workflows/codeql-analysis.yml/badge.svg?branch=core)](https://github.com/Circut-labs/AES-Python/actions/workflows/codeql-analysis.yml) [![Build & Upload Python Package](https://github.com/Circut-labs/AES-Python/actions/workflows/python-publish.yml/badge.svg)](https://github.com/Circut-labs/AES-Python/actions/workflows/python-publish.yml) + +- [AES-Python](#aes-python) + - [Installation](#installation) + - [Usage](#usage) + +Installation +--- +``` +pip install aes-python +``` + +Usage +--- +``` +AES_Python +``` diff --git a/pyproject.toml b/pyproject.toml index e19ff0d..d4388b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,19 @@ [build-system] -requires = ["setuptools>=42.0", "wheel"] +requires = ["setuptools>=63.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "AES-Python" -version = "1.0.2" +dynamic = ["version"] description = "AES (Advanced Encryption Standard) implementation in Python-3" -readme = "README.md" -authors = [{ name = "Gabriel Lindeblad", email = "Gabriel.Lindeblad@icloud.com" }] -license = { file = "LICENSE" } +readme = "index.md" +authors = [{name = "Gabriel Lindeblad "}] +license = {text = "MIT License (MIT)"} classifiers = [ + "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", + "Environment :: Console", + "Topic :: Education", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.9", @@ -19,11 +22,16 @@ classifiers = [ keywords = ["AES", "AES_Python", "AES-Python", "Advanced Encryption Standard", "encryption", "cryptography"] requires-python = ">=3.7" +[tools.setuptools.dynamic] +version = {attr = "AES_Python.__version__"} + [project.optional-dependencies] dev = ["pytest", "pytest-cov", "mypy", "flake8", "tox"] [project.urls] Homepage = "https://github.com/Circut-labs/AES-Python" +Repository = "https://github.com/Circut-labs/AES-Python" +Documentation = "https://github.com/Circut-labs/AES-Python" [project.scripts] AES_Python = "AES_Python.__main__:main" diff --git a/setup.cfg b/setup.cfg index a560196..8198daf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ +[metadata] +version = attr: AES_Python.__version__ + [options.package_data] AES_Python = py.typed diff --git a/src/AES_Python.egg-info/PKG-INFO b/src/AES_Python.egg-info/PKG-INFO index 5f2b405..d74a3d6 100644 --- a/src/AES_Python.egg-info/PKG-INFO +++ b/src/AES_Python.egg-info/PKG-INFO @@ -1,33 +1,17 @@ Metadata-Version: 2.1 Name: AES-Python -Version: 1.0.0 +Version: 1.1.0 Summary: AES (Advanced Encryption Standard) implementation in Python-3 -Author-email: Gabriel Lindeblad -License: MIT License - - Copyright (c) 2022 Circut Labs - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - +Author: Gabriel Lindeblad +License: MIT License (MIT) Project-URL: Homepage, https://github.com/Circut-labs/AES-Python +Project-URL: Repository, https://github.com/Circut-labs/AES-Python +Project-URL: Documentation, https://github.com/Circut-labs/AES-Python Keywords: AES,AES_Python,AES-Python,Advanced Encryption Standard,encryption,cryptography +Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent +Classifier: Environment :: Console +Classifier: Topic :: Education Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.9 @@ -39,28 +23,20 @@ License-File: LICENSE # AES-Python -![Tests](https://github.com/Circut-labs/AES-Python/actions/workflows/test.yml/badge.svg) +![Tests](https://github.com/Circut-labs/AES-Python/actions/workflows/test.yml/badge.svg) [![CodeQL](https://github.com/Circut-labs/AES-Python/actions/workflows/codeql-analysis.yml/badge.svg?branch=core)](https://github.com/Circut-labs/AES-Python/actions/workflows/codeql-analysis.yml) [![Build & Upload Python Package](https://github.com/Circut-labs/AES-Python/actions/workflows/python-publish.yml/badge.svg)](https://github.com/Circut-labs/AES-Python/actions/workflows/python-publish.yml) - [AES-Python](#aes-python) - - [About](#about) - - [Implemented running modes](#implemented-running-modes) - - [More information](#more-information) + - [Installation](#installation) + - [Usage](#usage) -About +Installation --- -This project is a way for me to structure and work on my gymnasium project where i implement the AES encryption algorithm in python to the evaluate it and write a report about it. This project is written in pure python and do not use any external libraries for the core module "AES_Module". The project and the code is not intended for any use outside of educational and no security can be guaranteed for any encryption and/or decryption that uses this implementation. Do also not that this is not going to be the most efficient implementation due to my limited coding skills and knowledge, but i have tried to do my best to make it as efficient as possible so it´s not to slow to work with. +``` +pip install aes-python +``` -This implementation of AES (Advanced Encryption Algorithm) algorithm is implemented with a number of different running modes such as ECB and CBC with more coming soon (see list below for updated information)... - -Implemented running modes ---- -- [x] **ECB** - Electronic Code Book mode (For more information see ...) -- [x] **CBC** - Cipher Block Chaining mode (For more information see ...) -- [ ] **CFB** - Cipher Feedback mode (For more information see ...) -- [ ] **OFB** - Output FeedBack mode (For more information see ...) -- [ ] **CTR** - Counter mode (For more information see ...) -- [ ] **GCM** - Galois/Counter mode (For more information see ...) - -More information +Usage --- -... +``` +AES_Python +``` diff --git a/src/AES_Python/AES.py b/src/AES_Python/AES.py index 1c7f5f5..92aaf66 100644 --- a/src/AES_Python/AES.py +++ b/src/AES_Python/AES.py @@ -59,6 +59,23 @@ # --------------- # Main action functions # --------------- +# Progress bar display and update +def progress_bar(progress, total_progress, terminal_width): + percent = 100 * (float(progress) / float(total_progress)) + + bar_width = terminal_width - 10 + bar_progress = int(bar_width * (float(progress) / float(total_progress))) + + if bar_progress > bar_width or percent > 100: + bar_progress = bar_width + percent = 100 + + bar_remaining = bar_width - bar_progress + bar = '#' * bar_progress + '-' * bar_remaining + print(f"\r[{bar}] {percent:.2f}%", end="\r") + return progress + 16 + + # Xtime def xtime(a): return (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1) @@ -220,7 +237,7 @@ def decryption_rounds(data, key): # --------------- -# Key expantion setup +# Key expansion setup # --------------- # Key expansion function (returns a list of round keys) def keyExpansion(key): @@ -351,14 +368,17 @@ def SubWord(word): # Running modes setup # --------------- # ECB encryption function -def ecb_enc(key, file_path): +def ecb_enc(key, file_path, terminal_width=110): file_size = getsize(file_path) + progress = 0 + progress = progress_bar(progress, file_size, terminal_width) with open(f"{file_path}.enc", 'wb') as output, open(file_path, 'rb') as data: for i in range(int(file_size/16)): raw = [i for i in data.read(16)] result = bytes(encryption_rounds(raw, key)) output.write(result) + progress = progress_bar(progress, file_size, terminal_width) if file_size % 16 != 0: raw = [i for i in data.read()] @@ -368,15 +388,20 @@ def ecb_enc(key, file_path): identifier = bytes(encryption_rounds([0 for i in range(15)] + [length], key)) output.write(result + identifier) + progress = progress_bar(progress, file_size, terminal_width) else: identifier = bytes(encryption_rounds([0 for i in range(16)], key)) output.write(identifier) + progress = progress_bar(progress, file_size, terminal_width) + progress = progress_bar(progress, file_size, terminal_width) remove(file_path) # ECB decryption function -def ecb_dec(key, file_path): +def ecb_dec(key, file_path, terminal_width=110): file_size = getsize(file_path) + progress = 0 + progress = progress_bar(progress, file_size, terminal_width) file_name = file_path[:-4] with open(f"{file_name}", 'wb') as output, open(file_path, 'rb') as data: @@ -384,6 +409,7 @@ def ecb_dec(key, file_path): raw = [i for i in data.read(16)] result = bytes(decryption_rounds(raw, key)) output.write(result) + progress = progress_bar(progress, file_size, terminal_width) data_pice = [i for i in data.read(16)] identifier = [i for i in data.read()] @@ -394,12 +420,16 @@ def ecb_dec(key, file_path): result = bytes(remove_padding(result, identifier)) output.write(result) + progress = progress_bar(progress, file_size, terminal_width) + progress = progress_bar(progress, file_size, terminal_width) remove(file_path) # CBC encryption function -def cbc_enc(key, file_path, iv): +def cbc_enc(key, file_path, iv, terminal_width=110): file_size = getsize(file_path) + progress = 0 + progress = progress_bar(progress, file_size, terminal_width) vector = [int(iv[i:i+2], 16) for i in range(0, len(iv), 2)] with open(f"{file_path}.enc", 'wb') as output, open(file_path, 'rb') as data: @@ -408,6 +438,7 @@ def cbc_enc(key, file_path, iv): raw = xor(raw, vector) vector = encryption_rounds(raw, key) output.write(bytes(vector)) + progress = progress_bar(progress, file_size, terminal_width) if file_size % 16 != 0: raw = [i for i in data.read()] @@ -420,17 +451,22 @@ def cbc_enc(key, file_path, iv): identifier = encryption_rounds(identifier, key) output.write(bytes(vector + identifier)) + progress = progress_bar(progress, file_size, terminal_width) else: identifier = xor([0 for i in range(16)], vector) identifier = bytes(encryption_rounds(identifier, key)) output.write(identifier) + progress = progress_bar(progress, file_size, terminal_width) + progress = progress_bar(progress, file_size, terminal_width) remove(file_path) # CBC decryption function -def cbc_dec(key, file_path, iv): +def cbc_dec(key, file_path, iv, terminal_width=110): iv = [int(iv[i:i+2], 16) for i in range(0, len(iv), 2)] file_size = getsize(file_path) + progress = 0 + progress = progress_bar(progress, file_size, terminal_width) file_name = file_path[:-4] with open(f"{file_name}", 'wb') as output, open(file_path, 'rb') as data: @@ -439,6 +475,7 @@ def cbc_dec(key, file_path, iv): raw = decryption_rounds(vector, key) result = xor(raw, iv) output.write(bytes(result)) + progress = progress_bar(progress, file_size, terminal_width) for i in range(int(file_size/16) - 3): raw = [i for i in data.read(16)] @@ -446,6 +483,7 @@ def cbc_dec(key, file_path, iv): result = xor(result, vector) vector = raw output.write(bytes(result)) + progress = progress_bar(progress, file_size, terminal_width) else: vector = iv @@ -461,4 +499,89 @@ def cbc_dec(key, file_path, iv): result = bytes(remove_padding(data_pice, identifier)) output.write(result) + progress = progress_bar(progress, file_size, terminal_width) + progress = progress_bar(progress, file_size, terminal_width) + remove(file_path) + + +# PCBC encryption function +def pcbc_enc(key, file_path, iv, terminal_width=110): + file_size = getsize(file_path) + progress = 0 + progress = progress_bar(progress, file_size, terminal_width) + vector = [int(iv[i:i+2], 16) for i in range(0, len(iv), 2)] + + with open(f"{file_path}.enc", 'wb') as output, open(file_path, 'rb') as data: + for i in range(int(file_size/16)): + raw = [i for i in data.read(16)] + tmp = xor(raw, vector) + vector = encryption_rounds(tmp, key) + output.write(bytes(vector)) + vector = xor(vector, raw) + progress = progress_bar(progress, file_size, terminal_width) + + if file_size % 16 != 0: + raw = [i for i in data.read()] + raw, length = add_padding(raw) + + tmp = xor(raw, vector) + vector1 = encryption_rounds(tmp, key) + vector = xor(vector1, raw) + + identifier = xor(([0 for i in range(15)] + [length]), vector) + identifier = encryption_rounds(identifier, key) + + output.write(bytes(vector1 + identifier)) + progress = progress_bar(progress, file_size, terminal_width) + else: + identifier = xor([0 for i in range(16)], vector) + identifier = bytes(encryption_rounds(identifier, key)) + output.write(identifier) + progress = progress_bar(progress, file_size, terminal_width) + progress = progress_bar(progress, file_size, terminal_width) + remove(file_path) + + +# PCBC decryption function +def pcbc_dec(key, file_path, iv, terminal_width=110): + iv = [int(iv[i:i+2], 16) for i in range(0, len(iv), 2)] + file_size = getsize(file_path) + progress = 0 + progress = progress_bar(progress, file_size, terminal_width) + 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 = [i for i in data.read(16)] + raw = decryption_rounds(vector, key) + result = xor(raw, iv) + vector = xor(vector, result) + output.write(bytes(result)) + progress = progress_bar(progress, file_size, terminal_width) + + for i in range(int(file_size/16) - 3): + raw = [i for i in data.read(16)] + result = decryption_rounds(raw, key) + result = xor(result, vector) + vector = xor(raw, result) + output.write(bytes(result)) + progress = progress_bar(progress, file_size, terminal_width) + else: + vector = iv + + data_pice = [i for i in data.read(16)] + vector_1, identifier = data_pice, [i for i in data.read()] + + result = decryption_rounds(data_pice, key) + data_pice = xor(result, vector) + + vector_1 = xor(vector_1, data_pice) + identifier = decryption_rounds(identifier, key) + identifier = xor(identifier, vector_1) + + result = bytes(remove_padding(data_pice, identifier)) + + output.write(result) + progress = progress_bar(progress, file_size, terminal_width) + progress = progress_bar(progress, file_size, terminal_width) remove(file_path) diff --git a/src/AES_Python/__init__.py b/src/AES_Python/__init__.py index cfbfdfd..0065c30 100644 --- a/src/AES_Python/__init__.py +++ b/src/AES_Python/__init__.py @@ -19,7 +19,7 @@ __copyright__ = 'Copyright 2022, Circut Labs' __credits__ = [""] __license__ = 'MIT' -__version__ = '1.0.2' +__version__ = '1.1.0' __maintainer__ = 'Gabriel Lindeblad' __email__ = 'Gabriel.lindeblad@icloud.com' __status__ = 'Development' diff --git a/src/AES_Python/__main__.py b/src/AES_Python/__main__.py index 949e8a1..50f6976 100644 --- a/src/AES_Python/__main__.py +++ b/src/AES_Python/__main__.py @@ -1,5 +1,7 @@ from AES_Python.encrypt import encrypt from AES_Python.decrypt import decrypt +from getpass import getpass +from os import get_terminal_size import AES_Python @@ -14,7 +16,7 @@ def main(): __/ | |___/ """) print("-"*66) - print(f"Version: {AES_Python.__version__} {AES_Python.__copyright__}") + print(f"Version: {AES_Python.__version__} {AES_Python.__copyright__}") print("-"*66) print("""This is a simple AES (Advanced Encryption Standard) implementation in Python-3. It is a pure Python implementation of AES that is @@ -28,16 +30,16 @@ def main(): def run(): action = input("Do you want to encrypt, decrypt or quit? (e/d/q): ") if action == "e": - running_mode = input("Please select cipher running mode (ECB/CBC/CFB/OFB/CTR/GCM): ") + running_mode = input("Please select cipher running mode (ECB/CBC/PCBC/CFB/OFB/CTR/GCM): ") if running_mode == "ECB": - key = input("Please enter your key: ") + key = getpass(prompt="Please enter your key: ") file_path = input("Please enter path to file: ") confirmation = input("Are you sure you want to encrypt this file? (y/n): ") if confirmation == "y": - encrypt(key, file_path, running_mode) - print("Encryption complete!") + encrypt(key, file_path, running_mode, terminal_size=get_terminal_size()[0]) + print("\nEncryption complete!") elif confirmation == "n": print("Encryption aborted!") @@ -47,15 +49,15 @@ def run(): print("Invalid input!") exit() - elif running_mode in ["CBC", "CFB", "OFB", "CTR", "GCM"]: - key = input("Please enter your key: ") - iv = input("Please enter your iv: ") + elif running_mode in ["CBC", "PCBC", "CFB", "OFB", "CTR", "GCM"]: + key = getpass(prompt="Please enter your key: ") + iv = getpass(prompt="Please enter your iv: ") file_path = input("Please enter path to file: ") confirmation = input("Are you sure you want to encrypt this file? (y/n): ") if confirmation == "y": - decrypt(key, file_path, running_mode, iv) - print("Encryption complete!") + encrypt(key, file_path, running_mode, iv, get_terminal_size()[0]) + print("\nEncryption complete!") elif confirmation == "n": print("Encryption aborted!") @@ -70,14 +72,53 @@ def run(): run() elif action == "d": - pass + running_mode = input("Please select cipher running mode (ECB/CBC/PCBC/CFB/OFB/CTR/GCM): ") + + if running_mode == "ECB": + key = getpass(prompt="Please enter your key: ") + file_path = input("Please enter path to file: ") + confirmation = input("Are you sure you want to decrypt this file? (y/n): ") + + if confirmation == "y": + decrypt(key, file_path, running_mode, terminal_size=get_terminal_size()[0]) + print("\nDecryption complete!") + + elif confirmation == "n": + print("Decryption aborted!") + exit() + + else: + print("Invalid input!") + exit() + + elif running_mode in ["CBC", "PCBC", "CFB", "OFB", "CTR", "GCM"]: + key = getpass(prompt="Please enter your key: ") + iv = getpass(prompt="Please enter your iv: ") + file_path = input("Please enter path to file: ") + confirmation = input("Are you sure you want to decrypt this file? (y/n): ") + + if confirmation == "y": + decrypt(key, file_path, running_mode, iv, get_terminal_size()[0]) + print("\nDecryption complete!") + + elif confirmation == "n": + print("Decryption aborted!") + exit() + + else: + print("Invalid input!") + exit() + + else: + print("Invalid cipher running mode") + run() elif action == "q": print("Exiting...") exit() else: - print("Invalid action (to cancel enter 'q')") + print("Invalid action (to quit enter 'q')") run() diff --git a/src/AES_Python/decrypt.py b/src/AES_Python/decrypt.py index 64c0751..a793dd2 100644 --- a/src/AES_Python/decrypt.py +++ b/src/AES_Python/decrypt.py @@ -1,30 +1,31 @@ from AES_Python import AES - from sys import argv # --------------- # Decryption function # --------------- -def decrypt(key, file_path, running_mode, iv=None): +def decrypt(key, file_path, running_mode, iv=None, terminal_size=110): # Input validation if file_path[-4:] != ".enc": raise Exception('File is not encrypted in known format') if (len(key) / 2) not in [16, 24, 32]: raise Exception('Key length is not valid') - elif running_mode in ["CBC", "CFB", "OFB", "CTR", "GCM"]: + elif running_mode in ["CBC", "PCBC", "CFB", "OFB", "CTR", "GCM"]: if (len(iv) / 2) != 16 or iv is None: raise Exception('IV length is not valid') # Running mode selection if running_mode == "ECB": - AES.ecb_dec(key, file_path) + AES.ecb_dec(key, file_path, terminal_size) elif running_mode == "CBC" and iv is not None: - AES.cbc_dec(key, file_path, iv) + AES.cbc_dec(key, file_path, iv, terminal_size) + elif running_mode == "PCBC" and iv is not None: + AES.pcbc_dec(key, file_path, iv, terminal_size) else: raise Exception("Running mode not supported") if __name__ == "__main__": - decrypt(key=argv[0], file_path=argv[1], running_mode=argv[2], iv=argv[3]) + decrypt(key=argv[1], file_path=argv[2], running_mode=argv[3], iv=argv[4]) diff --git a/src/AES_Python/encrypt.py b/src/AES_Python/encrypt.py index 062281b..7a9dc84 100644 --- a/src/AES_Python/encrypt.py +++ b/src/AES_Python/encrypt.py @@ -5,23 +5,25 @@ # --------------- # Encryption function # --------------- -def encrypt(key, file_path, running_mode, iv=None): +def encrypt(key, file_path, running_mode, iv=None, terminal_size=110): # Input validation if (len(key) / 2) not in [16, 24, 32]: raise Exception('Key length is not valid') - elif running_mode in ["CBC", "CFB", "OFB", "CTR", "GCM"]: + elif running_mode in ["CBC", "PCBC", "CFB", "OFB", "CTR", "GCM"]: if (len(iv) / 2) != 16 or iv is None: raise Exception('IV length is not valid') # Running mode selection if running_mode == "ECB": - AES.ecb_enc(key, file_path) + AES.ecb_enc(key, file_path, terminal_size) elif running_mode == "CBC" and iv is not None: - AES.cbc_enc(key, file_path, iv) + AES.cbc_enc(key, file_path, iv, terminal_size) + elif running_mode == "PCBC" and iv is not None: + AES.pcbc_enc(key, file_path, iv, terminal_size) else: raise Exception("Running mode not supported") if __name__ == "__main__": - encrypt(key=argv[0], file_path=argv[1], running_mode=argv[2], iv=argv[3]) + encrypt(key=argv[1], file_path=argv[2], running_mode=argv[3], iv=argv[4]) diff --git a/tests/Analyze.py b/tests/Analyze.py index 80bfe20..489713f 100644 --- a/tests/Analyze.py +++ b/tests/Analyze.py @@ -1,17 +1,18 @@ -import AES_Python.AES as AES +from AES_Python.encrypt import encrypt +from AES_Python.decrypt import decrypt def main(i): key = "2b7e151628aed2a6abf7158809cf4f3c" iv = "000102030405060708090a0b0c0d0e0f" - running_mode = "CBC" - file_path = r"/Users/gabriellindeblad/Documents/GitHub/AES-Python/tmp/test_files/data.txt" + running_mode = "PCBC" + file_path = r"C:\Users\Gabriel\Documents\GitHub\AES-Python\tmp\test_files\data.txt" if i == "enc": - AES.encrypt(key, file_path, running_mode, iv) + encrypt(key, file_path, running_mode, iv) else: file_path += ".enc" - AES.decrypt(key, file_path, running_mode, iv) + decrypt(key, file_path, running_mode, iv) if __name__ == '__main__': diff --git a/tests/test_decryption.py b/tests/test_decryption.py index 600a64c..f24307d 100644 --- a/tests/test_decryption.py +++ b/tests/test_decryption.py @@ -51,4 +51,30 @@ def test_aes_decryption_CBC(data, key, file_name, iv, expected): os.remove(file_name) - assert result == expected \ No newline at end of file + assert result == expected + +@pytest.mark.parametrize("data,key,file_name,iv,expected", [ + # 128 bit + (b'\xe4\xa7\x0e\xbd\x84\xfa\xf5\xd8`\xb8\xa1\x10\x0b~\xadhJ\xa98\x9f\xceZ\xd4\x9f"\xde\x00\xf6w\xa9\x1b\x05', "2b7e151628aed2a6abf7158809cf4f3c", "tmp1.txt", "000102030405060708090a0b0c0d0e0f", b'1234567890'), + (b'\x1b\x16\x86:\xb9*w\xc5)"\xe4\xe9D\\\xf1\xeeD\xc2\x1d\x19\x93\xd4\x7f\xed\xc8\xb8\xa1\xb60ow\xdd', "2b7e151628aed2a6abf7158809cf4f3c", "tmp2.txt", "000102030405060708090a0b0c0d0e0f", b'1234567890123456'), + (b'\x1b\x16\x86:\xb9*w\xc5)"\xe4\xe9D\\\xf1\xee\x0e\xcez\xe5\xcde\x91Q7\xc3|\x8bB\xe6\x96\xc0\x0e%0).\x8006\xf7V\xa1P\xf4\xec\xc0\x05', "2b7e151628aed2a6abf7158809cf4f3c", "tmp7.txt", "000102030405060708090a0b0c0d0e0f", b'12345678901234567890'), + (b'\x1b\x16\x86:\xb9*w\xc5)"\xe4\xe9D\\\xf1\xeeg\x19\xcc\xa3\x86K\xfax\xae\n\xee!k\xcc\xcb\xf2:\xfe\xa7,9jJo\xf6/q\xce\xec\x8b\xfd\xee\xc6\xee]\x9f\xbf\xcb~\x84\x8b\xd6\xe6\xed\xba\xbe\xbb.', "2b7e151628aed2a6abf7158809cf4f3c", "tmp8.txt", "000102030405060708090a0b0c0d0e0f", b'1234567890123456789012345678901234567890'), + # 192 bit + (b"\x89\x8fwWh\xaf\xfb@\xc9\xc3\xc0w\x81\xf7\x0e\xd3s\xee\xdf\xa7\xaf\x9f\xddV\x92\x18\x11\r'\xc1\x8d\xfa", "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "tmp3.txt", "000102030405060708090a0b0c0d0e0f", b'1234567890'), + (b'5\xb9\x19\x1dd\xf3e\xd7EP\x01^8\xb0\xf6\xfb\xe6\xc0\x12\xd1\xfa\x0fr\xde\xc5\xc4\xb9\x9a\xcc\xcd\x9d\xea', "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "tmp4.txt", "000102030405060708090a0b0c0d0e0f", b'1234567890123456'), + # 256 bit + (b'\x9dT\xb7B\x19e\xb8q\xc95\xfa\x80L\x88.9\x97]D\xd8L\xf7\x7f\xc4D\xb3\xbe\xb7\xe1\x81\xe5/', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "tmp5.txt", "000102030405060708090a0b0c0d0e0f", b'1234567890'), + (b'a\xfdIRQ\xf8\xf1D\xcc\xbf\x89\xc8\xd6\xec\x01;\xe3\xba\xba{-\xbdz\xa0r\x9c\xd6\xed\x86\xa0\xe2\xee', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "tmp6.txt", "000102030405060708090a0b0c0d0e0f", b'1234567890123456') +]) +def test_aes_decryption_PCBC(data, key, file_name, iv, expected): + with open(f"{file_name}.enc", "wb") as file: + file.write(data) + + decrypt(key, f"{file_name}.enc", "PCBC", iv) + + with open(file_name, "rb") as file: + result = file.read() + + os.remove(file_name) + + assert result == expected diff --git a/tests/test_encryption.py b/tests/test_encryption.py index 6917fba..c63b607 100644 --- a/tests/test_encryption.py +++ b/tests/test_encryption.py @@ -48,4 +48,28 @@ def test_aes_encrypt_CBC(data, key, file_name, iv, expected): os.remove(f"{file_name}.enc") + assert result == expected + +@pytest.mark.parametrize("data,key,file_name,iv,expected", [ + # 128 bit + (b'1234567890', "2b7e151628aed2a6abf7158809cf4f3c", "tmp.txt", "000102030405060708090a0b0c0d0e0f", b'\xe4\xa7\x0e\xbd\x84\xfa\xf5\xd8`\xb8\xa1\x10\x0b~\xadhJ\xa98\x9f\xceZ\xd4\x9f"\xde\x00\xf6w\xa9\x1b\x05'), + (b'1234567890123456', "2b7e151628aed2a6abf7158809cf4f3c", "tmp1.txt", "000102030405060708090a0b0c0d0e0f", b'\x1b\x16\x86:\xb9*w\xc5)"\xe4\xe9D\\\xf1\xeeD\xc2\x1d\x19\x93\xd4\x7f\xed\xc8\xb8\xa1\xb60ow\xdd'), + # 192 bit + (b'1234567890', "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "tmp2.txt", "000102030405060708090a0b0c0d0e0f", b"\x89\x8fwWh\xaf\xfb@\xc9\xc3\xc0w\x81\xf7\x0e\xd3s\xee\xdf\xa7\xaf\x9f\xddV\x92\x18\x11\r'\xc1\x8d\xfa"), + (b'1234567890123456', "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", "tmp3.txt", "000102030405060708090a0b0c0d0e0f", b'5\xb9\x19\x1dd\xf3e\xd7EP\x01^8\xb0\xf6\xfb\xe6\xc0\x12\xd1\xfa\x0fr\xde\xc5\xc4\xb9\x9a\xcc\xcd\x9d\xea'), + # 256 bit + (b'1234567890', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "tmp4.txt", "000102030405060708090a0b0c0d0e0f", b'\x9dT\xb7B\x19e\xb8q\xc95\xfa\x80L\x88.9\x97]D\xd8L\xf7\x7f\xc4D\xb3\xbe\xb7\xe1\x81\xe5/'), + (b'1234567890123456', "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "tmp5.txt", "000102030405060708090a0b0c0d0e0f", b'a\xfdIRQ\xf8\xf1D\xcc\xbf\x89\xc8\xd6\xec\x01;\xe3\xba\xba{-\xbdz\xa0r\x9c\xd6\xed\x86\xa0\xe2\xee') +]) +def test_aes_encrypt_PCBC(data, key, file_name, iv, expected): + with open(file_name, "wb") as file: + file.write(data) + + encrypt(key, file_name, "PCBC", iv) + + with open(f"{file_name}.enc", "rb") as file: + result = file.read() + + os.remove(f"{file_name}.enc") + assert result == expected \ No newline at end of file diff --git a/tmp/test.py b/tmp/test.py index 383649c..f1ee994 100644 --- a/tmp/test.py +++ b/tmp/test.py @@ -1,162 +1,23 @@ -sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, - 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, - 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, - 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, - 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, - 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, - 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, - 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, - 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, - 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, - 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, - 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, - 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, - 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, - 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, - 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16] +from os import get_terminal_size -Rcon = [0x00000000, 0x01000000, 0x02000000, - 0x04000000, 0x08000000, 0x10000000, - 0x20000000, 0x40000000, 0x80000000, - 0x1b000000, 0x36000000] +def progress_bar(progress, total_progress): + percent = 100 * (float(progress) / float(total_progress)) + terminal_width = get_terminal_size()[0] + bar_width = terminal_width - 10 + bar_progress = int(bar_width * (float(progress) / float(total_progress))) -# --------------- -# Key expantion setup -# --------------- -# Key expansion function -def keyExpansion(key): - # Key expansion setup - if len(key) == 16: - words = key_schedule(key, 4, 11) - nr = 11 - if len(key) == 24: - words = key_schedule(key, 6, 13) - nr = 13 - if len(key) == 32: - words = key_schedule(key, 8, 15) - nr = 15 + if bar_progress > bar_width or percent > 100: + bar_progress = bar_width + percent = 100 - round_keys = [None for i in range(nr)] + bar_remaining = bar_width - bar_progress + bar = '#' * bar_progress + '-' * bar_remaining + print(f"\r[{bar}] {percent:.2f}%", end="\r") + return progress + 16 - #words = [[t for t in range(4)] for i in range(nr * 4)] - tmp = [None for i in range(4)] - for i in range(nr * 4): - for t in range(4): - tmp[t] = int(words[i][t], 16) - words[i] = tuple(tmp) - - for i in range(nr): - round_keys[i] = (words[i * 4] + words[i * 4 + 1] + words[i * 4 + 2] + words[i * 4 + 3]) - - return round_keys - - -# Key schedule (nk = number of colums, nr = number of rounds) -def key_schedule(key, nk, nr): - # Create list and populates first nk words with key - words = [(key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]) for i in range(nk)] - - # fill out the rest based on previews words, rotword, subword and rcon values - limit = False - for i in range(nk, (nr * nk)): - # get required previous keywords - temp, word = words[i-1], words[i-nk] - - # if multiple of nk use rot, sub, rcon etc - if i % nk == 0: - x = SubWord(RotWord(temp)) - rcon = Rcon[int(i/nk)] - temp = hexor(x, hex(rcon)[2:]) - limit = False - elif i % 4 == 0: - limit = True - - if i % 4 == 0 and limit and nk >= 8: - temp = SubWord(temp) - - # xor the two hex values - xord = hexor(''.join(word), ''.join(temp)) - words.append((xord[:2], xord[2:4], xord[4:6], xord[6:8])) - return words - - -# takes two hex values and calculates hex1 xor hex2 -def hexor(hex1, hex2): - # convert to binary - bin1 = hex2binary(hex1) - bin2 = hex2binary(hex2) - - # calculate - xord = int(bin1, 2) ^ int(bin2, 2) - - # cut prefix - hexed = hex(xord)[2:] - - # leading 0s get cut above, if not length 8 add a leading 0 - if len(hexed) != 8: - hexed = '0' + hexed - - return hexed - - -# takes a hex value and returns binary -def hex2binary(hex): - return bin(int(str(hex), 16)) - - -# takes from 1 to the end, adds on from the start to 1 -def RotWord(word): - return word[1:] + word[:1] - - -# selects correct value from sbox based on the current word -def SubWord(word): - sWord = [] - - # loop throug the current word - for i in range(4): - - # check first char, if its a letter(a-f) get corresponding decimal - # otherwise just take the value and add 1 - if word[i][0].isdigit() is False: - row = ord(word[i][0]) - 86 - else: - row = int(word[i][0])+1 - - # repeat above for the seoncd char - if word[i][1].isdigit() is False: - col = ord(word[i][1]) - 86 - else: - col = int(word[i][1])+1 - - # get the index base on row and col (16x16 grid) - sBoxIndex = (row*16) - (17-col) - - # get the value from sbox without prefix - piece = hex(sbox[sBoxIndex])[2:] - - # check length to ensure leading 0s are not forgotton - if len(piece) != 2: - piece = '0' + piece - - sWord.append(piece) - - # return string - return ''.join(sWord) - - -def main(): - # hardcoding input key for demonstration purposes, could be read in from user/program via cmd/gui etc. - key = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f"] - - # expand key - w = keyExpansion(key) - - # display nicely - print("Key provided: " + "".join(key)) - print(w) - - -if __name__ == '__main__': - main() +if __name__ == "__main__": + progress = 0 + for i in range(100): + progress = progress_bar(progress, 100) + print() diff --git a/tmp/test_3.py b/tmp/test_3.py index 18c6fbb..86a587a 100644 --- a/tmp/test_3.py +++ b/tmp/test_3.py @@ -1,4 +1,4 @@ -with open(r"/Users/gabriellindeblad/Documents/GitHub/AES-Python/tmp/test_files/data.txt.enc", "rb") as p: +with open(r".\tmp\test_files\data.txt.enc", "rb") as p: t = p.read() print(t) diff --git a/tmp/test_files/data.txt b/tmp/test_files/data.txt index 6f785a9..6a537b5 100644 --- a/tmp/test_files/data.txt +++ b/tmp/test_files/data.txt @@ -1 +1 @@ -1234567890123456789012345678901234567890 \ No newline at end of file +1234567890 \ No newline at end of file