-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Restructure project for Python3 (#22)
* Add unittest * Restructuring Use abstract base class for PadChecker Add description for PadChecker Reorganizing * Add logger * Add python help tools * Clarify vulnerable encryption service in examples * Fix tests * Fix typo in github action * Remove flake8 from github actions * Add pytest to dev requirements * Add structlog to base requirements * Update the readme
- Loading branch information
1 parent
d6588ff
commit 22cf701
Showing
17 changed files
with
310 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# http:https://editorconfig.org | ||
|
||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
insert_final_newline = true | ||
trim_trailing_whitespace = true | ||
indent_style = space | ||
|
||
[*.{py,rst,ini}] | ||
indent_size = 4 | ||
|
||
[*.py] | ||
max_line_length=119 | ||
multi_line_output=3 | ||
default_section=THIRDPARTY | ||
|
||
[*.md] | ||
trim_trailing_whitespace = false | ||
|
||
[Makefile] | ||
indent_style = tab | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[settings] | ||
line_length=119 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
repos: | ||
- repo: https://github.com/ambv/black | ||
rev: stable | ||
hooks: | ||
- id: black | ||
language_version: python3.7 | ||
- repo: https://github.com/pre-commit/pre-commit-hooks | ||
rev: v1.2.3 | ||
hooks: | ||
- id: flake8 | ||
- repo: https://github.com/pre-commit/mirrors-isort | ||
rev: v4.3.4 | ||
hooks: | ||
- id: isort | ||
language_version: python3.7 |
This file was deleted.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
from abc import abstractmethod | ||
|
||
import structlog | ||
|
||
from .exceptions import PadDownException | ||
|
||
logger = structlog.get_logger(__name__) | ||
|
||
|
||
class PadChecker: | ||
""" | ||
Create a subclass of PadChecker to pass to DecryptEngine | ||
""" | ||
|
||
@abstractmethod | ||
def has_valid_padding(self, ciphertext): | ||
""" | ||
Override this method to check if the padding of the ciphertext is valid | ||
:param bytes ciphertext: The ciphertext to check | ||
:rtype: True for valid padding, False otherwise. | ||
""" | ||
raise PadDownException("Not implemented") | ||
|
||
|
||
class DecryptEngine: | ||
def __init__(self, pad_checker: PadChecker, blocksize: int = 16): | ||
if not isinstance(pad_checker, PadChecker): | ||
raise PadDownException(f"pad_checker not an instance of {PadChecker}") | ||
self.pad_checker = pad_checker | ||
self.blocksize = blocksize | ||
|
||
def decrypt_at_index(self, ciphertext: bytearray, index: int): | ||
if not isinstance(ciphertext, bytearray): | ||
raise PadDownException(f"ciphertext not an instance of {bytearray}") | ||
|
||
# Replace ciphertext at index with a guessed byte | ||
ciphertext_temp = ciphertext | ||
for guess in range(256): | ||
ciphertext_temp[index] = guess | ||
if self.pad_checker.has_valid_padding(ciphertext_temp): | ||
return guess | ||
|
||
raise RuntimeError("[!] Found no valid padding, is PadChecker implemented correctly?") | ||
|
||
def decrypt_block(self, block): | ||
if not isinstance(block, bytearray): | ||
raise PadDownException(f"block not an instance of {bytearray}") | ||
|
||
c_previous = bytearray(b"\x00" * self.blocksize) | ||
intermediate = bytearray(b"\x00" * self.blocksize) | ||
for i in range(self.blocksize): | ||
for j in range(i): | ||
c_previous[(self.blocksize - 1) - j] = intermediate[(self.blocksize - 1) - j] ^ (i + 1) | ||
|
||
c_prime = self.decrypt_at_index(c_previous + block, (self.blocksize - 1) - i) | ||
intermediate[(self.blocksize - 1) - i] = c_prime ^ (i + 1) | ||
print("intermediate: {}".format([hex(x)[2:] for x in intermediate])) | ||
return intermediate | ||
|
||
def get_intermediate(self, ciphertext) -> bytes: | ||
key = b"" | ||
blocks = len(ciphertext) // self.blocksize | ||
|
||
# Iterate blocks last to first | ||
for i in range(blocks): | ||
block_start = len(ciphertext) - (i + 1) * self.blocksize | ||
block_end = len(ciphertext) - (i * self.blocksize) | ||
key = self.decrypt_block(ciphertext[block_start:block_end]) + key | ||
return key | ||
|
||
def decrypt(self, ciphertext) -> bytes: | ||
if not isinstance(ciphertext, bytes): | ||
raise Exception(f"Ciphertext {type(ciphertext)} not an instance of {bytes}") | ||
|
||
logger.debug(f"Ciphertext length: {len(ciphertext)}") | ||
logger.debug(f"Blocks to decrypt: {len(ciphertext) // self.blocksize}") | ||
|
||
# Convert ciphertext to mutable bytearray | ||
ciphertext = bytearray(ciphertext) | ||
|
||
key = self.get_intermediate(ciphertext) | ||
plaintext = bytearray() | ||
for i in range(len(ciphertext) - self.blocksize): | ||
b = ciphertext[i] ^ key[i + self.blocksize] | ||
plaintext += (b).to_bytes(1, byteorder="big") | ||
return plaintext |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from Crypto.Cipher import AES | ||
from Crypto.Util.Padding import pad, unpad | ||
|
||
|
||
class InvalidPadding(BaseException): | ||
pass | ||
|
||
|
||
class VulnerableEncryptionService: | ||
""" | ||
Used to simulate a vulnerable black box encryption service. | ||
The service is vulnerable to Padding oracle attack | ||
""" | ||
|
||
key = b"0123456789ABCDEF" # Secret key, only known to service | ||
iv = b"FEDCBA9876543210" # Public IV, usually prepended to ciphertext | ||
|
||
def encrypt(self, plaintext): | ||
""" | ||
Encrypts plaintext in 16 block AES in with CBC mode | ||
:param bytes plaintext: Plaintext to be encrypted | ||
:rtype bytes: Returns ciphertext | ||
""" | ||
cipher = AES.new(self.key, AES.MODE_CBC, IV=self.iv) | ||
return cipher.encrypt(pad(plaintext, 16)) | ||
|
||
def decrypt(self, ciphertext): | ||
""" | ||
This functions decrypts, but does not return the plaintext. | ||
However, an error is returned if the PKCS7 padding is invalid. This allows for Padding Oracle Attack. | ||
Thus, it can used to decrypt arbitrary ciphertexts | ||
:param bytes ciphertext: 16 block ciphertext | ||
:rtype str: Returns 'Decryption successful!' on successful decryption and unpadding | ||
:raises InvalidPadding: If the PKCS7 padding is invalid. | ||
""" | ||
cipher = AES.new(self.key, AES.MODE_CBC, IV=self.iv) | ||
try: | ||
unpad(cipher.decrypt(ciphertext), 16) | ||
except ValueError: | ||
raise InvalidPadding("Invalid PKCS7 Padding") | ||
return "Decryption successful!" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
class PadDownException(Exception): | ||
pass |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from Crypto.Util.Padding import unpad | ||
from PadDown.decrypt_engine import DecryptEngine, PadChecker | ||
from PadDown.examples.vulnerable_encryption_service import InvalidPadding, VulnerableEncryptionService | ||
|
||
VEC = VulnerableEncryptionService() | ||
|
||
|
||
class TestVulnerableEncryptionService: | ||
def test_encryption_and_decryption(self): | ||
plaintext_misaligned = b"Misaligned plaintext!" | ||
ciphertext = VEC.encrypt(plaintext_misaligned) | ||
answer = VEC.decrypt(ciphertext) | ||
assert answer == "Decryption successful!" | ||
|
||
|
||
class TestDecryptEngine: | ||
def test_decrypt_at_index(self): | ||
class TestPadChecker(PadChecker): | ||
def has_valid_padding(self, ciphertext): | ||
return ciphertext == b"\x05" | ||
|
||
decrypt_engine = DecryptEngine(TestPadChecker()) | ||
decrypt_engine.decrypt_at_index(bytearray(b"\x00"), 0) | ||
|
||
def test_decrypt_block(self): | ||
class TestPadChecker(PadChecker): | ||
def has_valid_padding(self, ciphertext): | ||
return ciphertext == b"\x05" | ||
|
||
def test_complete_run(self): | ||
plaintext_original = bytearray("This is a padded plaintext", encoding="ascii") | ||
|
||
# Assert that the plaintext is padded | ||
assert len(plaintext_original) % 16 != 0 | ||
|
||
ciphertext = VEC.encrypt(plaintext_original) | ||
|
||
class MyPadChecker(PadChecker): | ||
def has_valid_padding(self, ciphertext): | ||
try: | ||
VEC.decrypt(ciphertext) | ||
return True | ||
except InvalidPadding: | ||
return False | ||
return False | ||
|
||
decrypt_engine = DecryptEngine(MyPadChecker()) | ||
plaintext_decrypted = decrypt_engine.decrypt(VEC.iv + ciphertext) | ||
assert plaintext_original == unpad(plaintext_decrypted, 16) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,29 @@ | ||
# PadDown | ||
CBC PKCS7 Padding Oracle attack engine | ||
PadDown is an AES CBC PKCS7 [Padding Oracle Attack](https://en.wikipedia.org/wiki/Padding_oracle_attack) engine. It simplifies performing [Padding Oracle Attack](https://en.wikipedia.org/wiki/Padding_oracle_attack) on a vulnerable encryption service. This is useful for both CTF and real-world attacks, where you are in possession of a ciphertext, and have a so called Padding Oracle available. | ||
|
||
## Usage | ||
* Using PadDown is as easy as implementing `PadChecker` class containing the ``hasValidPadding(...)`` method retuning a `bool`. As argument it takes ciphertext to test against the Padding Oracle. Have your implementation return ``True`` if you receive no padding error and ``False`` otherwise. | ||
|
||
Implement a class containing the ``hasValidPadding(...)`` method. As argument it takes the binary data that is the test ciphertext. Return ``True`` if the padding is valid and ``False`` otherwise. | ||
* Now you are ready to instatiate the ``DecryptEngine(...)`` class, and start decrypting your ciphertext. | ||
|
||
Input to the ``DecryptEngine.decrypt`` must be the raw bytes. | ||
Examples can be found in the `PadDown/examples` directory. | ||
|
||
## Development | ||
|
||
|
||
The project can be setup with | ||
```bash | ||
python3 -m venv .venv | ||
.venv/bin/activate | ||
pip install -r requirements/dev.txt | ||
``` | ||
|
||
### Pull requests | ||
We are open to pull requests. | ||
|
||
We use [black](https://github.com/psf/black), [flake8](https://flake8.pycqa.org/en/latest/) and [isort](https://github.com/timothycrosley/isort) for linting, and implement unit testing using [pytest](https://docs.pytest.org/en/latest/). A [pre-commit](https://pre-commit.com/) configuration file has been added, for checking against these linters before comitting. | ||
|
||
Please squash all commits before submitting a pull request. | ||
|
||
## Testing | ||
To run the unittests, simply run `pytest`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[tool.black] | ||
line-length = 119 | ||
include = '\.pyi?$' | ||
exclude = ''' | ||
/( | ||
\.git | ||
| \.tox | ||
| venv | ||
| _build | ||
| buck-out | ||
| build | ||
| dist | ||
| migrations | ||
)/ | ||
''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pycryptodome==3.9.0 | ||
structlog==20.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
-r base.txt | ||
black | ||
flake8 | ||
isort | ||
pytest | ||
pre-commit |
Oops, something went wrong.