This shows you the differences between two versions of the page.
ic:labs:06 [2022/09/27 00:51] razvan.smadu [Lab Code] |
ic:labs:06 [2023/10/09 23:22] (current) razvan.smadu |
||
---|---|---|---|
Line 5: | Line 5: | ||
Prezentarea PowerPoint pentru acest laborator poate fi găsită [[https://drive.google.com/file/d/1YFkD5I6yNgVf1y9bhK4xdR2xZgDZ4Dd1/view?usp=sharing|aici]]. | Prezentarea PowerPoint pentru acest laborator poate fi găsită [[https://drive.google.com/file/d/1YFkD5I6yNgVf1y9bhK4xdR2xZgDZ4Dd1/view?usp=sharing|aici]]. | ||
+ | Puteți lucra acest laborator folosind platforma Google Colab, accesând acest [[https://colab.research.google.com/github/ACS-IC-labs/IC-labs/blob/main/labs/lab06/lab6.ipynb|link]]. | ||
+ | |||
+ | |||
+ | <hidden> | ||
+ | <spoiler Click pentru a vedea utils.py> | ||
<file python utils.py> | <file python utils.py> | ||
import base64 | import base64 | ||
- | + | from typing import Generator | |
- | # CONVERSION FUNCTIONS | + | |
- | def _chunks(string, chunk_size): | + | |
- | for i in range(0, len(string), chunk_size): | + | |
- | yield string[i:i+chunk_size] | + | |
- | def byte_2_bin(bval): | + | |
- | """ | + | def _pad(data: str, size: int) -> str: |
- | Transform a byte (8-bit) value into a bitstring | + | reminder = len(data) % size |
+ | if reminder != 0: | ||
+ | data = "0" * (size - reminder) + data | ||
+ | return data | ||
+ | |||
+ | |||
+ | def _chunks(data: str, chunk_size: int) -> Generator[str, None, None]: | ||
+ | data = _pad(data, chunk_size) | ||
+ | for i in range(0, len(data), chunk_size): | ||
+ | yield data[i : i + chunk_size] | ||
+ | |||
+ | |||
+ | def _hex(data: int) -> str: | ||
+ | return format(data, "02x") | ||
+ | |||
+ | |||
+ | # Conversion functions | ||
+ | |||
+ | |||
+ | def byte_2_bin(bval: int) -> str: | ||
+ | """Converts a byte value to a binary string. | ||
+ | |||
+ | Args: | ||
+ | bval (int): | ||
+ | The byte value to be converted. It should be an integer between | ||
+ | 0 and 255. | ||
+ | |||
+ | Returns: | ||
+ | str: The binary string representation of the byte value, where each bit | ||
+ | is encoded as a character. The result has a fixed length of 8 characters | ||
+ | and is padded with leading zeros if necessary. | ||
+ | |||
+ | Examples: | ||
+ | >>> byte_2_bin(72) | ||
+ | '01001000' | ||
+ | >>> byte_2_bin(66) | ||
+ | '01000010' | ||
""" | """ | ||
return bin(bval)[2:].zfill(8) | return bin(bval)[2:].zfill(8) | ||
- | + | ||
- | def _hex(x): | + | |
- | return format(x, '02x') | + | def hex_2_bin(data: str) -> str: |
- | + | """Converts a hexadecimal string to a binary representation. | |
- | def hex_2_bin(data): | + | |
- | return ''.join(f'{int(x, 16):08b}' for x in _chunks(data, 2)) | + | Args: |
- | + | data (str): The hexadecimal string to be converted. It should have an | |
- | def str_2_bin(data): | + | even number of characters and only contain valid hexadecimal digits |
- | return ''.join(f'{ord(c):08b}' for c in data) | + | (0-9, A-F, a-f). |
- | + | ||
- | def bin_2_hex(data): | + | Returns: |
- | return ''.join(f'{int(b, 2):02x}' for b in _chunks(data, 8)) | + | str: The binary representation of the hexadecimal string, where each |
- | + | pair of hexadecimal digits is encoded as an 8-bit binary number. | |
- | def str_2_hex(data): | + | |
- | return ''.join(f'{ord(c):02x}' for c in data) | + | Examples: |
- | + | >>> hex_2_bin("01abcd") | |
- | def bin_2_str(data): | + | '000000011010101111001101' |
- | return ''.join(chr(int(b, 2)) for b in _chunks(data, 8)) | + | >>> hex_2_bin("0a") |
- | + | '00001010' | |
- | def hex_2_str(data): | + | """ |
- | return ''.join(chr(int(x, 16)) for x in _chunks(data, 2)) | + | return "".join(f"{int(x, 16):08b}" for x in _chunks(data, 2)) |
- | + | ||
- | # XOR FUNCTIONS | + | |
- | def strxor(a, b): # xor two strings, trims the longer input | + | def bin_2_hex(data: str) -> str: |
- | return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)) | + | """Converts a binary string to a hexadecimal representation. |
- | + | ||
- | def bitxor(a, b): # xor two bit-strings, trims the longer input | + | Args: |
- | return ''.join(str(int(x) ^ int(y)) for (x, y) in zip(a, b)) | + | data (str): The binary string to be converted. It should have a multiple |
- | + | of 8 characters and only contain valid binary digits (0 or 1). | |
- | def hexxor(a, b): # xor two hex-strings, trims the longer input | + | |
- | return ''.join(_hex(int(x, 16) ^ int(y, 16)) for (x, y) in zip(_chunks(a, 2), _chunks(b, 2))) | + | Returns: |
- | + | str: The hexadecimal representation of the binary string, where each | |
- | # BASE64 FUNCTIONS | + | group of 8 binary digits is encoded as a pair of hexadecimal digits. |
- | def b64decode(data): | + | |
- | return bytes_to_string(base64.b64decode(string_to_bytes(data))) | + | Examples: |
- | + | >>> bin_2_hex("000000011010101111001101") | |
- | def b64encode(data): | + | '01abcd' |
+ | >>> bin_2_hex("00001010") | ||
+ | '0a' | ||
+ | """ | ||
+ | return "".join(f"{int(b, 2):02x}" for b in _chunks(data, 8)) | ||
+ | |||
+ | |||
+ | def str_2_bin(data: str) -> str: | ||
+ | """Converts a string to a binary representation. | ||
+ | |||
+ | Args: | ||
+ | data (str): The string to be converted. | ||
+ | |||
+ | Returns: | ||
+ | str: The binary representation of the string, where each character is | ||
+ | encoded as an 8-bit binary number. | ||
+ | |||
+ | Examples: | ||
+ | >>> str_2_bin("Hello") | ||
+ | '0100100001100101011011000110110001101111' | ||
+ | >>> str_2_bin("IC") | ||
+ | '0100100101000011' | ||
+ | """ | ||
+ | return "".join(f"{ord(c):08b}" for c in data) | ||
+ | |||
+ | |||
+ | def bin_2_str(data: str) -> str: | ||
+ | """Converts a binary string to a string. | ||
+ | |||
+ | Args: | ||
+ | data (str): The binary string to be converted. It should have a multiple | ||
+ | of 8 characters and only contain valid binary digits (0 or 1). | ||
+ | |||
+ | Returns: | ||
+ | str: The string representation of the binary string, where each group | ||
+ | of 8 binary digits is decoded as a character. | ||
+ | |||
+ | Examples: | ||
+ | >>> bin_2_str("0100100001100101011011000110110001101111") | ||
+ | 'Hello' | ||
+ | >>> bin_2_str("0100100101000011") | ||
+ | 'IC' | ||
+ | """ | ||
+ | return "".join(chr(int(b, 2)) for b in _chunks(data, 8)) | ||
+ | |||
+ | |||
+ | def str_2_hex(data: str) -> str: | ||
+ | """Converts a string to a hexadecimal representation. | ||
+ | |||
+ | Args: | ||
+ | data (str): The string to be converted. | ||
+ | |||
+ | Returns: | ||
+ | str: The hexadecimal representation of the string, where each character | ||
+ | is encoded as a pair of hexadecimal digits. | ||
+ | |||
+ | Examples: | ||
+ | >>> str_2_hex("Hello") | ||
+ | '48656c6c6f' | ||
+ | >>> str_2_hex("IC") | ||
+ | '4943' | ||
+ | """ | ||
+ | return "".join(f"{ord(c):02x}" for c in data) | ||
+ | |||
+ | |||
+ | def hex_2_str(data: str) -> str: | ||
+ | """Converts a hexadecimal string to a string. | ||
+ | |||
+ | Args: | ||
+ | data (str): The hexadecimal string to be converted. It should have an | ||
+ | even number of characters and only contain valid hexadecimal digits | ||
+ | (0-9, A-F, a-f). | ||
+ | |||
+ | Returns: | ||
+ | str: The string representation of the hexadecimal string, where each | ||
+ | pair of hexadecimal digits is decoded as a character. | ||
+ | |||
+ | Examples: | ||
+ | >>> hex_2_str("48656c6c6f") | ||
+ | 'Hello' | ||
+ | >>> hex_2_str("4943") | ||
+ | 'IC' | ||
+ | """ | ||
+ | return "".join(chr(int(x, 16)) for x in _chunks(data, 2)) | ||
+ | |||
+ | |||
+ | # XOR functions | ||
+ | |||
+ | |||
+ | def strxor(operand_1: str, operand_2: str) -> str: | ||
+ | """Performs a bitwise exclusive OR (XOR) operation on two strings. | ||
+ | |||
+ | Args: | ||
+ | operand_1 (str): The first string to be XORed. | ||
+ | operand_2 (str): The second string to be XORed. | ||
+ | |||
+ | Returns: | ||
+ | str: The result of the XOR operation on the two strings, where each | ||
+ | character is encoded as an 8-bit binary number. The result has | ||
+ | the same length as the shorter input string. | ||
+ | |||
+ | Examples: | ||
+ | >>> strxor("Hello", "IC") | ||
+ | '\\x01&' | ||
+ | >>> strxor("secret", "key") | ||
+ | '\\x18\\x00\\x1a' | ||
+ | """ | ||
+ | return "".join(chr(ord(x) ^ ord(y)) for (x, y) in zip(operand_1, operand_2)) | ||
+ | |||
+ | |||
+ | def bitxor(operand_1: str, operand_2: str) -> str: | ||
+ | """Performs a bitwise exclusive OR (XOR) operation on two bit-strings. | ||
+ | |||
+ | Args: | ||
+ | operand_1 (str): The first bit-string to be XORed. It should only | ||
+ | contain valid binary digits (0 or 1). | ||
+ | operand_2 (str): The second bit-string to be XORed. It should only | ||
+ | contain valid binary digits (0 or 1). | ||
+ | |||
+ | Returns: | ||
+ | str: The result of the XOR operation on the two bit-strings, where each | ||
+ | bit is encoded as a character. The result has the same length as | ||
+ | the shorter input bit-string. | ||
+ | |||
+ | Examples: | ||
+ | >>> bitxor("01001000", "01000010") | ||
+ | '00001010' | ||
+ | >>> bitxor("10101010", "00110011") | ||
+ | '10011001' | ||
+ | """ | ||
+ | return "".join(str(int(x) ^ int(y)) for (x, y) in zip(operand_1, operand_2)) | ||
+ | |||
+ | |||
+ | def hexxor(operand_1: str, operand_2: str) -> str: | ||
+ | """Performs a bitwise exclusive OR (XOR) operation on two hexadecimal | ||
+ | strings. | ||
+ | |||
+ | Args: | ||
+ | operand_1 (str): The first hexadecimal string to be XORed. It should | ||
+ | have an even number of characters and only contain valid hexadecimal | ||
+ | digits (0-9, A-F, a-f). | ||
+ | operand_2 (str): The second hexadecimal string to be XORed. It should | ||
+ | have an even number of characters and only contain valid | ||
+ | digits (0-9, A-F, a-f). | ||
+ | |||
+ | Returns: | ||
+ | str: The result of the XOR operation on the two hexadecimal strings, | ||
+ | where each pair of hexadecimal digits is encoded as a pair of | ||
+ | hexadecimal digits. The result has the same length as the shorter | ||
+ | input hexadecimal string. | ||
+ | |||
+ | Examples: | ||
+ | >>> hexxor("48656c6c6f", "42696e67") | ||
+ | '0a0c020b' | ||
+ | >>> hexxor("736563726574", "6b6579") | ||
+ | '18001a' | ||
+ | """ | ||
+ | return "".join( | ||
+ | _hex(int(x, 16) ^ int(y, 16)) | ||
+ | for (x, y) in zip(_chunks(operand_1, 2), _chunks(operand_2, 2)) | ||
+ | ) | ||
+ | |||
+ | |||
+ | # Python3 'bytes' functions | ||
+ | |||
+ | |||
+ | def bytes_to_string(bytes_data: bytearray | bytes) -> str: | ||
+ | """Converts a byte array or a byte string to a string. | ||
+ | |||
+ | Args: | ||
+ | bytes_data (bytearray | bytes): The byte array or the byte string to be | ||
+ | converted. It should be encoded in Latin-1 format. | ||
+ | |||
+ | Returns: | ||
+ | str: The string representation of the byte array or the byte string, | ||
+ | decoded using Latin-1 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> bytes_to_string(b'Hello') | ||
+ | 'Hello' | ||
+ | >>> bytes_to_string(bytearray(b'IC')) | ||
+ | 'IC' | ||
+ | """ | ||
+ | return bytes_data.decode(encoding="raw_unicode_escape") | ||
+ | |||
+ | |||
+ | def string_to_bytes(string_data: str) -> bytes: | ||
+ | """Converts a string to a byte string. | ||
+ | |||
+ | Args: | ||
+ | string_data (str): The string to be converted. | ||
+ | |||
+ | Returns: | ||
+ | bytes: The byte string representation of the string, encoded using | ||
+ | Latin-1 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> string_to_bytes('Hello') | ||
+ | b'Hello' | ||
+ | >>> string_to_bytes('IC') | ||
+ | b'IC' | ||
+ | """ | ||
+ | return string_data.encode(encoding="raw_unicode_escape") | ||
+ | |||
+ | |||
+ | # Base64 functions | ||
+ | |||
+ | |||
+ | def b64encode(data: str) -> str: | ||
+ | """Encodes a string to base64. | ||
+ | |||
+ | Parameters: | ||
+ | data (str): The string to be encoded. | ||
+ | |||
+ | Returns: | ||
+ | str: The base64 encoded string, using Latin-1 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> b64encode("Hello") | ||
+ | 'SGVsbG8=' | ||
+ | >>> b64encode("IC") | ||
+ | 'SUM=' | ||
+ | """ | ||
return bytes_to_string(base64.b64encode(string_to_bytes(data))) | return bytes_to_string(base64.b64encode(string_to_bytes(data))) | ||
- | + | ||
- | # PYTHON3 'BYTES' FUNCTIONS | + | |
- | def bytes_to_string(bytes_data): | + | def b64decode(data: str) -> str: |
- | return bytes_data.decode() # default utf-8 | + | """Decodes a base64 encoded string. |
- | + | ||
- | def string_to_bytes(string_data): | + | Args: |
- | return string_data.encode() # default utf-8 | + | data (str): The base64 encoded string to be decoded. It should only |
+ | contain valid base64 characters (A-Z, a-z, 0-9, +, /, =). | ||
+ | |||
+ | Returns: | ||
+ | str: The decoded string, using Latin-1 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> b64decode("SGVsbG8=") | ||
+ | 'Hello' | ||
+ | >>> b64decode("SUM=") | ||
+ | 'IC' | ||
+ | """ | ||
+ | return bytes_to_string(base64.b64decode(string_to_bytes(data))) | ||
</file> | </file> | ||
+ | </spoiler> | ||
==== AES ECB ==== | ==== AES ECB ==== | ||
Line 89: | Line 361: | ||
<note tip> | <note tip> | ||
- | Funcția **findBlockSize()** mărește numărul de caractere adăugate, mărind lungimea mesajului. | + | Funcția **find_block_size()** mărește numărul de caractere adăugate, mărind lungimea mesajului. |
Cum putem asocia dimensiunea mesajului cu numărul de blocuri criptate? | Cum putem asocia dimensiunea mesajului cu numărul de blocuri criptate? | ||
Line 147: | Line 419: | ||
==== Lab Code ==== | ==== Lab Code ==== | ||
+ | <spoiler Click pentru a vedea lab06.py> | ||
<file python lab06.py> | <file python lab06.py> | ||
- | from math import ceil | ||
import base64 | import base64 | ||
- | import os | + | from math import ceil |
- | from random import randint | + | from typing import List, Tuple |
- | from Crypto.Cipher import AES | + | |
- | from utils import * | + | |
- | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes | + | |
from cryptography.hazmat.backends import default_backend | from cryptography.hazmat.backends import default_backend | ||
+ | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes | ||
+ | from utils import * | ||
+ | |||
backend = default_backend() | backend = default_backend() | ||
- | def split_bytes_in_blocks(x, block_size): | + | def split_bytes_in_blocks(x: bytes, block_size: int) -> List[bytes]: |
- | nb_blocks = ceil(len(x)/block_size) | + | """Splits a byte string into a list of blocks of equal size. |
- | return [x[block_size*i:block_size*(i+1)] for i in range(nb_blocks)] | + | |
+ | Args: | ||
+ | x (bytes): The byte string to split. | ||
+ | block_size (int): The size of each block in bytes. | ||
- | def pkcs7_padding(message, block_size): | + | Returns: |
+ | List[bytes]: A list of byte strings, each of length block_size, | ||
+ | except for the last one which may be shorter. | ||
+ | """ | ||
+ | nb_blocks = ceil(len(x) / block_size) | ||
+ | return [x[block_size * i : block_size * (i + 1)] for i in range(nb_blocks)] | ||
+ | |||
+ | |||
+ | def pkcs7_padding(message: bytes, block_size: int) -> bytes: | ||
+ | """Applies PKCS#7 padding to a byte string. | ||
+ | |||
+ | Args: | ||
+ | message (bytes): The byte string to pad. | ||
+ | block_size (int): The size of the block in bytes. | ||
+ | |||
+ | Returns: | ||
+ | bytes: A byte string that is a multiple of block_size in length, | ||
+ | with padding bytes added at the end. The value of each padding | ||
+ | byte is equal to the number of padding bytes added. | ||
+ | """ | ||
padding_length = block_size - (len(message) % block_size) | padding_length = block_size - (len(message) % block_size) | ||
if padding_length == 0: | if padding_length == 0: | ||
Line 172: | Line 466: | ||
- | def pkcs7_strip(data): | + | def pkcs7_strip(data: bytes) -> bytes: |
+ | """Removes PKCS#7 padding from a byte string. | ||
+ | |||
+ | Args: | ||
+ | data (bytes): The byte string to strip. | ||
+ | |||
+ | Returns: | ||
+ | bytes: A byte string with the padding bytes removed from the end. | ||
+ | """ | ||
padding_length = data[-1] | padding_length = data[-1] | ||
- | return data[:- padding_length] | + | return data[:-padding_length] |
- | def encrypt_aes_128_ecb(msg, key): | + | def encrypt_aes_128_ecb(plaintext: bytes, key: bytes) -> bytes: |
- | padded_msg = pkcs7_padding(msg, block_size=16) | + | """Encrypts a byte string using AES-128 in ECB mode. |
+ | |||
+ | Args: | ||
+ | plaintext (bytes): The byte string to encrypt. It will be padded | ||
+ | using PKCS#7. | ||
+ | key (bytes): The encryption key. It must be 16 bytes in length. | ||
+ | |||
+ | Returns: | ||
+ | bytes: A byte string that is the encrypted version of plaintext. | ||
+ | """ | ||
+ | padded_msg = pkcs7_padding(plaintext, block_size=16) | ||
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend) | cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend) | ||
encryptor = cipher.encryptor() | encryptor = cipher.encryptor() | ||
Line 184: | Line 496: | ||
- | def decrypt_aes_128_ecb(ctxt, key): | + | def decrypt_aes_128_ecb(ciphertext: bytes, key: bytes) -> bytes: |
+ | """Decrypts a byte string using AES-128 in ECB mode. | ||
+ | |||
+ | Args: | ||
+ | ciphertext (bytes): The byte string to decrypt. It must be a multiple of | ||
+ | 16 bytes in length. | ||
+ | key (bytes): The decryption key. It must be 16 bytes in length. | ||
+ | |||
+ | Returns: | ||
+ | bytes: A byte string that is the decrypted version of ciphertext. The | ||
+ | PKCS#7 padding will be removed. | ||
+ | """ | ||
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend) | cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend) | ||
decryptor = cipher.decryptor() | decryptor = cipher.decryptor() | ||
- | decrypted_data = decryptor.update(ctxt) + decryptor.finalize() | + | decrypted_data = decryptor.update(ciphertext) + decryptor.finalize() |
message = pkcs7_strip(decrypted_data) | message = pkcs7_strip(decrypted_data) | ||
return message | return message | ||
- | # You are not suppose to see this | + | |
class Oracle: | class Oracle: | ||
- | def __init__(self): | + | """A class that simulates an encryption oracle using AES-128 in ECB mode. |
- | self.key = 'Mambo NumberFive'.encode() | + | You are not suppose to see this""" |
- | self.prefix = 'PREF'.encode() | + | |
- | self.target = base64.b64decode( # You are suppose to break this | + | def __init__(self) -> None: |
- | "RG8gbm90IGxheSB1cCBmb3IgeW91cnNlbHZlcyB0cmVhc3VyZXMgb24gZWFydGgsIHdoZXJlIG1vdGggYW5kIHJ1c3QgZGVzdHJveSBhbmQgd2hlcmUgdGhpZXZlcyBicmVhayBpbiBhbmQgc3RlYWwsCmJ1dCBsYXkgdXAgZm9yIHlvdXJzZWx2ZXMgdHJlYXN1cmVzIGluIGhlYXZlbiwgd2hlcmUgbmVpdGhlciBtb3RoIG5vciBydXN0IGRlc3Ryb3lzIGFuZCB3aGVyZSB0aGlldmVzIGRvIG5vdCBicmVhayBpbiBhbmQgc3RlYWwuCkZvciB3aGVyZSB5b3VyIHRyZWFzdXJlIGlzLCB0aGVyZSB5b3VyIGhlYXJ0IHdpbGwgYmUgYWxzby4=" | + | self.key = "Mambo NumberFive".encode() |
+ | self.prefix = "PREF".encode() | ||
+ | |||
+ | # You are suppose to break this | ||
+ | self.target = base64.b64decode( | ||
+ | "RG8gbm90IGxheSB1cCBmb3IgeW91cnNlbHZlcyB0cmVhc3VyZXMgb24gZWFydGgsI" | ||
+ | "HdoZXJlIG1vdGggYW5kIHJ1c3QgZGVzdHJveSBhbmQgd2hlcmUgdGhpZXZlcyBicm" | ||
+ | "VhayBpbiBhbmQgc3RlYWwsCmJ1dCBsYXkgdXAgZm9yIHlvdXJzZWx2ZXMgdHJlYXN" | ||
+ | "1cmVzIGluIGhlYXZlbiwgd2hlcmUgbmVpdGhlciBtb3RoIG5vciBydXN0IGRlc3Ry" | ||
+ | "b3lzIGFuZCB3aGVyZSB0aGlldmVzIGRvIG5vdCBicmVhayBpbiBhbmQgc3RlYWwuC" | ||
+ | "kZvciB3aGVyZSB5b3VyIHRyZWFzdXJlIGlzLCB0aGVyZSB5b3VyIGhlYXJ0IHdpbG" | ||
+ | "wgYmUgYWxzby4=" | ||
) | ) | ||
- | def encrypt(self, message): | + | def encrypt(self, message: bytes) -> bytes: |
return encrypt_aes_128_ecb( | return encrypt_aes_128_ecb( | ||
self.prefix + message + self.target, | self.prefix + message + self.target, | ||
- | self.key | + | self.key, |
) | ) | ||
+ | |||
# Task 1 | # Task 1 | ||
- | def findBlockSize(): | + | def find_block_size() -> Tuple[int, int, int]: |
- | initialLength = len(Oracle().encrypt(b'')) | + | initial_length = len(Oracle().encrypt(b"")) |
i = 0 | i = 0 | ||
- | while 1: # Feed identical bytes of your-string to the function 1 at a time until you get the block length | + | |
- | # You will also need to determine here the size of fixed prefix + target + pad | + | block_size = 0 |
- | # And the minimum size of the plaintext to make a new block | + | size_of_prefix_target_padding = 0 |
- | length = len(Oracle().encrypt(b'X'*i)) | + | minimum_size_to_align_plaintext = 0 |
+ | |||
+ | while 1: | ||
+ | # Feed identical bytes of your-string to the function 1 at a time | ||
+ | # until you get the block length. You will also need to determine | ||
+ | # here the size of fixed prefix + target + pad, and the minimum | ||
+ | # size of the plaintext to make a new block | ||
+ | length = len(Oracle().encrypt(b"X" * i)) | ||
i += 1 | i += 1 | ||
- | return block_size, sizeOfPrefixTargetPadding, minimumSizeToAlighPlaintext | + | # TODO 1: find block_size, size_of_prefix_target_padding, |
+ | # and minimum_size_to_align_plaintext | ||
+ | break | ||
+ | |||
+ | return ( | ||
+ | block_size, | ||
+ | size_of_prefix_target_padding, | ||
+ | minimum_size_to_align_plaintext, | ||
+ | ) | ||
# Task 2 | # Task 2 | ||
- | def findPrefixSize(block_size): | + | def find_prefix_size(block_size: int) -> int: |
- | previous_blocks = None | + | initial_blocks = split_bytes_in_blocks(Oracle().encrypt(b""), block_size) |
- | # Find the situation where prefix_size + padding_size - 1 = block_size | + | |
- | # Use split_bytes_in_blocks to get blocks of size(block_size) | + | |
+ | # TODO 2: Find when prefix_size + padding_size - 1 = block_size | ||
+ | # Use split_bytes_in_blocks to get blocks of size block_size. | ||
+ | |||
+ | # TODO 2.1: Find the block containing the prefix by comparing | ||
+ | # initial_blocks and modified_blocks | ||
+ | # You may find enumerate() and zip() useful. | ||
+ | modified_blocks = split_bytes_in_blocks(Oracle().encrypt(b"X"), block_size) | ||
+ | prefix_block_index = 0 | ||
+ | |||
+ | # TODO 2.2: As now we know in which block to look, find when that block | ||
+ | # does not change anymore when adding more X's. The complementary will | ||
+ | # represent the prefix. | ||
+ | prefix_size_in_block = 0 | ||
+ | |||
+ | prefix_size = prefix_block_index * block_size + prefix_size_in_block | ||
return prefix_size | return prefix_size | ||
# Task 3 | # Task 3 | ||
- | def recoverOneByteAtATime(block_size, prefix_size, target_size): | + | def recover_one_byte_at_a_time( |
+ | block_size: int, | ||
+ | prefix_size: int, | ||
+ | target_size: int, | ||
+ | ) -> str: | ||
known_target_bytes = b"" | known_target_bytes = b"" | ||
+ | |||
for _ in range(target_size): | for _ in range(target_size): | ||
# prefix_size + padding_length + known_len + 1 = 0 mod block_size | # prefix_size + padding_length + known_len + 1 = 0 mod block_size | ||
- | known_len = len(know_target_bytes) | + | known_len = len(known_target_bytes) |
- | padding_length = (- known_len - 1 - prefix_size) % block_size | + | padding_length = (-known_len - 1 - prefix_size) % block_size |
padding = b"X" * padding_length | padding = b"X" * padding_length | ||
- | # target block plaintext contains only known characters except its last character | + | # TODO 3.1: Determine the target block index which contains only known |
- | # Don't forget to use split_bytes_in_blocks to get the correct block | + | # characters except its last character. |
+ | |||
+ | # TODO 3.2: Get the target block form split_bytes_in_blocks at the index | ||
+ | # previously determined. | ||
+ | |||
+ | # TODO 3.3: Try every possibility for the last character and search for | ||
+ | # the block that you already know. That character will be added to | ||
+ | # the known target bytes. | ||
+ | |||
+ | return known_target_bytes.decode() | ||
- | # trying every possibility for the last character | + | def main() -> None: |
+ | # Find block size, prefix size, and length of plaintext size to align blocks | ||
+ | ( | ||
+ | block_size, | ||
+ | size_of_prefix_target_padding, | ||
+ | minimum_size_to_align_plaintext, | ||
+ | ) = find_block_size() | ||
- | print(known_target_bytes.decode()) | + | print(f"Block size:\t\t\t\t{block_size}") |
+ | print( | ||
+ | "Size of prefix, target, and padding:" | ||
+ | f"\t{size_of_prefix_target_padding}" | ||
+ | ) | ||
+ | print(f"Pad needed to align:\t\t\t{minimum_size_to_align_plaintext}") | ||
+ | # Find size of the prefix | ||
+ | prefix_size = find_prefix_size(block_size) | ||
+ | print(f"\nPrefix Size:\t{prefix_size}") | ||
- | # Find block size, prefix size, and length of plaintext size to allign blocks | + | # Size of the target |
- | block_size, sizeOfPrefixTargetPadding, minimumSizeToAlignPlaintext = findBlockSize() | + | target_size = ( |
- | print("Block size:\t\t\t" + str(block_size)) | + | size_of_prefix_target_padding |
- | print("Size of prefix and target:\t" + str(sizeOfPrefixTargetPadding)) | + | - minimum_size_to_align_plaintext |
- | print("Pad needed to align:\t\t" + str(minimumSizeToAlignPlaintext)) | + | - prefix_size |
+ | ) | ||
- | # Find size of the prefix | + | # Recover the target |
- | prefix_size = findPrefixSize(block_size) | + | recovered_target = recover_one_byte_at_a_time( |
- | print("\nPrefix Size:\t" + str(prefix_size)) | + | block_size, |
+ | prefix_size, | ||
+ | target_size, | ||
+ | ) | ||
+ | print(f"\nTarget: {recovered_target}") | ||
- | # Size of the target | ||
- | target_size = sizeOfPrefixTargetPadding- \ | ||
- | minimumSizeToAlignPlaintext - prefix_size | ||
- | print("\nTarget:") | + | if __name__ == "__main__": |
- | recoverOneByteAtATime(block_size, prefix_size, target_size) | + | main() |
</file> | </file> | ||
+ | </spoiler> | ||
+ | </hidden> |