Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit 6468f46
Author: programmingAthlete <luca.bonamino@hotmail.com>
Date:   Wed Oct 25 20:48:34 2023 +0200

    block ciphers

commit de15f1b
Author: programmingAthlete <luca.bonamino@hotmail.com>
Date:   Wed Oct 25 20:47:28 2023 +0200

    block ciphers
  • Loading branch information
programmingAthlete committed Oct 25, 2023
1 parent f75fbb0 commit 61e219d
Show file tree
Hide file tree
Showing 27 changed files with 487 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ src/crypto_pkg.egg-info
src/crypto_pkg/__pycache__/
src/crypto_pkg/contracts/__pycache__/
src/crypto_pkg/number_theory/__pycache__/
src/crypto_pkg/rsa/__pycache__/
src/crypto_pkg/ciphers/asymmetric/rsa/__pycache__/
tests/__pycache__/


Expand Down
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
# crypto_pkg

Package with RSA and DGVH encryption schemes used.
Package containing symmetric and asymmetric key ciphers and attacks

This package is used by the [BruteSniffing_Fisher](https://github.com/programmingAthlete/BruteSniffing_Fisher) repository.
## Ciphers
<ul>
<li>Asymmetric Key (PKE)</li>
<ul>
<li>Textbook RSA</li>
<li>DGVH</li>
</ul>
<li>Symmetric key</li>
<ul>
<li>AES</li>
<li>Modified vulnerable version of AES - AES without shift rows</li>
</ul>
</ul>

## Attacks
The following attacks are on know plain text attacks.
<ul>
<li>Double encryption attack on AES</li>
<li>Key recovery on the modified version of AES</li>
</ul>

Usage examples are provided in the attacks source code files
<ul>
<li>attacks/block_ciphers/double_encryption.py</li>
<li>attacks/block_ciphers/modified_aes.py</li>
</ul>

## Usage
The <i>Textbook RSA</i> and the <i>DGVH</i> PKEs are used in the [BruteSniffing_Fisher](https://github.com/programmingAthlete/BruteSniffing_Fisher) repository.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pycryptodome==3.19.0
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages

__version__ = "0.0.2"
__version__ = "1.0.0"

with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
Expand Down
Binary file removed src/crypto_pkg/DGHV/__pycache__/__init__.cpython-38.pyc
Binary file not shown.
Binary file removed src/crypto_pkg/DGHV/__pycache__/dghv.cpython-38.pyc
Binary file not shown.
File renamed without changes.
Binary file not shown.
File renamed without changes.
Binary file not shown.
Binary file not shown.
56 changes: 56 additions & 0 deletions src/crypto_pkg/attacks/block_ciphers/double_encryption.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from typing import Dict, Tuple, Union

from Crypto.Cipher import AES

from crypto_pkg.attacks.block_ciphers.utils import Text, prepare_key


class DoubleAESAttack:

@staticmethod
def encrypt(key: Text, plain_text: Text) -> Text:
cipher = AES.new(key.ascii_hex, AES.MODE_ECB)
cipher_text = cipher.encrypt(plain_text.ascii_hex)
return Text(text=cipher_text)

@staticmethod
def decrypt(key: Text, cipher_text: Text) -> Text:
cipher = AES.new(key.ascii_hex, AES.MODE_ECB)
plain_text = cipher.decrypt(cipher_text.ascii_hex)
return Text(text=plain_text)

@classmethod
def lookup_table_computation(cls, plain_text: str) -> Dict[int, int]:
p = Text(text=bytes.fromhex(plain_text))
out = {cls.encrypt(key=prepare_key(i), plain_text=p).integer: i for i in range(1, 2 ** 24)}
return out

@classmethod
def search_match(cls, cipher_text: str, lookup_table: Dict[int, int]) -> Union[Tuple[Text, Text], None]:
c = Text(text=bytes.fromhex(cipher_text))

for i in range(2 ** 24):
key = prepare_key(i)
m = cls.decrypt(key=key, cipher_text=c)
if lookup_table.get(m.integer):
return prepare_key(lookup_table.get(m.integer)), key

@classmethod
def attack(cls, plain_text: str, cipher_text: str):

look_up_table = cls.lookup_table_computation(plain_text=plain_text)
keys = cls.search_match(cipher_text=cipher_text, lookup_table=look_up_table)
return keys


if __name__ == '__main__':

''' Example '''

pt = '2355502c48059b15f70ddf4938b3b97e'
ct = '5d64800bce91edda9c3bad2956be5b12'
ks = DoubleAESAttack.attack(plain_text=pt, cipher_text=ct)
if ks:
print("\nKeys found:")
print(f"\tk1: 0x{ks[0].hex}")
print(f"\tk2: 0x{ks[1].hex}")
95 changes: 95 additions & 0 deletions src/crypto_pkg/attacks/block_ciphers/modified_aes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import logging
from logging import getLogger
from multiprocessing import Pool
import random

from crypto_pkg.attacks.block_ciphers.utils import prepare_key
from crypto_pkg.ciphers.symmetric.aes import CustomAES, array_to_matrix, get_array_from_state

logging.basicConfig(level=logging.DEBUG)

_log = getLogger(__name__)
_log.setLevel(logging.DEBUG)


class ModifiedAES(CustomAES):

def aes_round_trans(self, plain_text, round_key=None, last=False):
state_matrix = array_to_matrix(plain_text)
# subbytes transformation
s = self.aes_sub_bytes(state_matrix)
# mixcolumns transformation
if last:
s_k = self.aes_add_round_key(s, round_key)
return get_array_from_state(s_k)
else:
c = self.aes_mix_columns(s)
s_k = self.aes_add_round_key(c, round_key)
return get_array_from_state(s_k)

def encrypt(self, plain_text, key):
ks = array_to_matrix(key)
state = array_to_matrix(plain_text)
s_k = self.aes_add_round_key(state, ks)

pn = get_array_from_state(s_k)
for i in range(1, 10):
tmp = self.aes_round_trans(plain_text=pn, round_key=ks)
pn = tmp
pn = self.aes_round_trans(plain_text=pn, round_key=ks, last=True)
return pn

def attack_section(self, plain_text, cipher_block_ref, init_pos, section_n=0):
for i in range(2 ** 32):
key = prepare_key(i, max_key=init_pos)
k_block = [int(item, 16) for item in [key.hex[i * 2:i * 2 + 2] for i in range(len(key.hex))] if item != '']
c = self.encrypt(key=k_block, plain_text=plain_text)
c_by_block = [c[i * 4:i * 4 + 4] for i in range(len(c))]
if c_by_block[section_n] == cipher_block_ref[section_n]:
_log.info(f"key guess for block {section_n}: {key.hex}")
return key

def attack(self, plain_text: str, cipher_text: str):
p_int_list = [int(item, 16) for item in [plain_text[i * 2:i * 2 + 2] for i in range(len(plain_text))] if item != '']
c_int_list = [int(item, 16) for item in [cipher_text[i * 2:i * 2 + 2] for i in range(len(cipher_text))] if item != '']

c_by_block_ref = [c_int_list[i * 4:i * 4 + 4] for i in range(len(c))]
args = (
[p_int_list, c_by_block_ref, 32, 0],
[p_int_list, c_by_block_ref, 64, 1],
[p_int_list, c_by_block_ref, 96, 2],
[p_int_list, c_by_block_ref, 128, 3]
)

_log.debug("Run attack on sub-blocks in parallel")
with Pool() as pool:
res = pool.starmap(self.attack_section, args)
_log.debug(f"Parallel execution terminated with keys guesses {res}")
r = [int(item.hex, 16) for item in res]
out = r[0] ^ r[1] ^ r[2] ^ r[3]
_log.info(f"128bits key guess: {out}")
return out


if __name__ == '__main__':
''' Example '''

# ---- Generation of the plain text - cipher text pair
# Choose the key
to_find_key = '00000001000000100000000000000a01'
# Choose a random plain text
pt = format(random.getrandbits(128), 'x')
# Prepare plain text and key for encryption
p = [int(item, 16) for item in [pt[i * 2:i * 2 + 2] for i in range(len(pt))] if item != '']
k = [int(item, 16) for item in [to_find_key[i * 2:i * 2 + 2] for i in range(len(to_find_key))] if item != '']
# Generate cipher text
aes = ModifiedAES()
c = aes.encrypt(key=k, plain_text=p)
ct = bytes(c).hex()

# ---- Run the attack
_log.debug(f"Run the attack with plain-text {p} and cipher-text {p}")
aes = ModifiedAES()
result = aes.attack(plain_text=pt, cipher_text=ct)
assert result == int(to_find_key, 16)
print(f"\nSuccess: key {to_find_key} recovered")
35 changes: 35 additions & 0 deletions src/crypto_pkg/attacks/block_ciphers/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class Text:
"""
Number object whose attributes are
ascii_hex: Hexadecimal ASCII representation
hex: string with Hexadecimal base representation
integer: integer number
"""

def __init__(self, text):
self.ascii_hex = text
self.hex = text.hex()
self.integer = int(text.hex(), 16)


def prepare_key(key: int, max_key=24) -> Text:
"""
Converts and integer to usable key. Appends 128-max_kex 0 bits after the integer and pads 0 bits before the number
to satisfy the required number of bits. Then it converts to hexadecimal and instantiate a Text object
Args:
key: integer number
max_key: after max_key bits, a sequence of 0 bits starts
Returns:
Text object corresponding to the prepared key.
"""
try:
k = bin(key)
k_n = k[2:] + (128 - max_key) * '0'
n_k = format(int(k_n, 2), 'x')
n_k2 = n_k.rjust(2 * 16, '0')
k_n = bytes.fromhex(n_k2)

except Exception as exc:
raise exc
return Text(text=k_n)
Empty file.
Binary file not shown.
Empty file.
File renamed without changes.
Empty file.
Empty file.
File renamed without changes.
Empty file.
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 61e219d

Please sign in to comment.