Differences

This shows you the differences between two versions of the page.

Link to this comparison view

ic:labs:06 [2020/11/06 12:16]
acosmin.maria
ic:labs:06 [2023/10/09 23:22] (current)
razvan.smadu
Line 1: Line 1:
 +**Inainte de laborator**:​ [[https://​youtu.be/​lLKspzbcBDY|Video Laborator 6]]
 +
 ===== Laboratorul 06 - AES Byte at a Time ECB Decryption ===== ===== Laboratorul 06 - AES Byte at a Time ECB Decryption =====
 +
 +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>​
 +import base64
 +from typing import Generator
 +
 +
 +def _pad(data: str, size: int) -> str:
 +    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)
 +
 +
 +def hex_2_bin(data:​ str) -> str:
 +    """​Converts a hexadecimal string to a binary representation.
 +
 +    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 binary representation of the hexadecimal string, where each
 +            pair of hexadecimal digits is encoded as an 8-bit binary number.
 +
 +    Examples:
 +        >>>​ hex_2_bin("​01abcd"​)
 +        '​000000011010101111001101'​
 +        >>>​ hex_2_bin("​0a"​)
 +        '​00001010'​
 +    """​
 +    return ""​.join(f"​{int(x,​ 16):​08b}"​ for x in _chunks(data,​ 2))
 +
 +
 +def bin_2_hex(data:​ str) -> str:
 +    """​Converts a binary string to a hexadecimal representation.
 +
 +    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 hexadecimal representation of the binary string, where each
 +            group of 8 binary digits is encoded as a pair of hexadecimal digits.
 +
 +    Examples:
 +        >>>​ bin_2_hex("​000000011010101111001101"​)
 +        '​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)))
 +
 +
 +def b64decode(data:​ str) -> str:
 +    """​Decodes a base64 encoded string.
 +
 +    Args:
 +        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>​
 +</​spoiler>​
  
 ==== AES ECB ==== ==== AES ECB ====
-The simplest of the encryption modes is the Electronic Codebook ​(ECB) mode (named after conventional physical codebooks). The message is divided into blocksand each block is encrypted separately+Cel mai simplu mod de criptare este ECB (Electronic Codebook). ​Mesajul este împărțit în blocurifiecare bloc fiind criptat separat.
  
 {{https://​upload.wikimedia.org/​wikipedia/​commons/​thumb/​d/​d6/​ECB_encryption.svg/​902px-ECB_encryption.svg.png?​450|CBC encryption}} {{https://​upload.wikimedia.org/​wikipedia/​commons/​thumb/​d/​d6/​ECB_encryption.svg/​902px-ECB_encryption.svg.png?​450|CBC encryption}}
 {{https://​upload.wikimedia.org/​wikipedia/​commons/​thumb/​e/​e6/​ECB_decryption.svg/​902px-ECB_decryption.svg.png?​450|CBC decryption}} {{https://​upload.wikimedia.org/​wikipedia/​commons/​thumb/​e/​e6/​ECB_decryption.svg/​902px-ECB_decryption.svg.png?​450|CBC decryption}}
  
-Since each block of plaintext is encrypted with the key independentlyidentical blocks of plaintext will yield identical blocks of ciphertext. +Ținând cont că fiecare bloc din mesaj este criptat individual cu cheia kblocuri identice din mesaj vor rezulta în blocuri criptate identicPutem astfel, în cazul în care folosim modul ECB, să ne așteptăm la multe porțiuni criptate repetate.
-Lots of people know that when you encrypt something in ECB modeyou can see penguins through it+
  
-The vulnerability happens when+Această vulnerabilitate apare când
-  - You send an INPUT to the server. +  - Trimitem un INPUT către ​server 
-  - The server appends ​secret ​to INPUT -> INPUT||secret +  - Serverul concatenează un mesaj secret INPUT -> INPUT||secret 
-  - The server encrypts it with a secret key and a prefix ​-> AES-128-ECB(random-prefix || attacker-controlled || target-bytes,​ random-key) +  - Serverul criptează mesajul trimis folosind propria cheie -> AES-128-ECB(random-prefix || attacker-controlled || target-bytes,​ random-key) 
-  - The server returns+  - Serverul trimite rezultatul criptat înapoi către noi.
  
-For the next exercises, we will use the following {{:​ic:​laboratoare:​aesecb_Attack.zip|code stub}}.+PREFIX poate fi un header de pachet sau orice altă informație "​inutilă"​.
  
-==== Exercise ​1 - Determining Block Size ==== +Pentru următoarele exerciții vom folosi {{:​ic:​laboratoare:​aesecb_Attack.zip|scheletul de laborator}}. 
-The first step in attacking a block-based cipher is to determine the size of the block+ 
-Feed identical bytes of your-string to the function 1 at time - start with 1 byte ("​A"​)then "​AA", ​then "​AAA" ​and so onDiscover the block size of the cipherYou know it, but do this step anyway.+==== Exercițiul ​1 - Determinarea dimensiunii blocului (3p) ==== 
 +Primul pas necesar, într-un atac asupra unei criptări pe blocuri, este determinarea dimensiunii unui bloc. Deși cunoaștem deja dimensiunea în acest caz, este un pas necesar în alte situații
 + 
 +Pentru ​afla dimensiunea,​ este suficient să trimitem, pe rând, mesaje din ce în ce mai mari ("​A",​ "​AA",​ "​AAA"​...).
 <note tip> <note tip>
-Function **findBlockSize()** keeps on incrementing the padding length (and the message length also). ​ 
  
-How does the message length relates to the number of cypher blocks?+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?
 </​note>​ </​note>​
  
-==== Exercise ​2 - Determining Prefix size ==== +==== Exercițiul ​2 - Determinarea dimensiunii prefixului (3p) ==== 
-We give some chosen plaintext of increasing length to the oracleWhen we detect a block that does not change with the addition of one more byte of chosen plaintextthis means this block only contains prefix and chosen plaintextEg:+Vom aborda această problemă la fel ca în pasul anteriorTrimitem mesaje din ce în ce mai mari. Atunci când blocul criptat corespunzător prefixului nu se va mai schimbaputem calcula lungimea sa.
 <​code>​ <​code>​
 RRTT TT RRTT TT
Line 36: Line 375:
 RRXT TTT RRXT TTT
 </​code>​ </​code>​
-Using R to denote the random prefix, X for the input we would give to the oracle (hereafter called the chosen plaintext) and T for target. 
  
-Now we know the pad length required to align the target ​to blocks+În exemplul anterior am notat cu R caracterele prefixului, X = caracterele mesajului input și T mesajul ​target ​(secretul pe care dorim să îl aflăm).
  
 <note tip> <note tip>
-We also know that (prefix + pad - 1) % block_size = 0 + 
 +Știm că avem lungimea ​(prefix + pad - 1) % block_size = 0 
 </​note>​ </​note>​
  
 <note important>​ <note important>​
-What happens if the prefix is longer than the block size?+Ce se întâmplă dacă lungimea prefixului este mai mare decât lungimea unui singur bloc?
 </​note>​ </​note>​
  
-==== Exercise ​3 - ECB Byte at a Time Attack ==== +==== Exercițiul ​3 - ECB Byte at a Time Attack ​(4p) ==== 
- +Presupunem că avem un algoritm de criptare-bloc care criptează ​16 bytes, producând ​ciphertext ​de 16 bytesFolosim acest algoritm pentru a cripta 2 blocuri de date necunoscute, m1 și m2. În plusavem voie să trimitem propriul input m0, care va fi lipit în fața acestor blocuri
-Suppose we have a block cipher that takes a 16 byte plaintext and produces a 16 byte ciphertext. ​We use this block cipher to encrypt two blocks worth of unknown datacall them m1 and m2. Additionally we are allowed to prepend some data to these two blockslet'call it m0 (we control this data)+Pentru că putem trimite orice mesaj, vom alege să trimitem unul de lungime ​16. Astfel, în cazul în care se folosește modul ECBputem afla Enc(m0). ​Având acces la perechi de input propriu - ciphertext poate fi foarte util în acest cazavând de fapt un oracolÎn cazul în care am trimite doar 15 bytes, ​putem afla ultimul byte prin brute force.
-Note that in this scheme nothing prevents us from choosing an m0 that is 16 bytes longThis means we effectively have an encryption oracle for a full blocksince the first block returned in this case would be Enc(m0) ​if ECB mode is being usedThis means we can get the encryption of arbitrary blocks of datawhich will come in handy. +
-We can set m0 equal to 15 known bytes, ​and if we have an encryption oracle we can brute force the last byte:+
 <​code>​ <​code>​
  Block 1          Block 2  Block 3  Block 1          Block 2  Block 3
Line 58: Line 395:
  ​|----known----||--m1---|  ​|----known----||--m1---|
 </​code>​ </​code>​
-We just have to send all 256 possible guesses for Block 1 to the encryption oracle and see which one matches the outputLet's say we get a match on the byte encoding ​"​w"​. ​We then repeat the process with a one byte shorter m0 to get the next byte in the same fashion:+Încercând toate cele 256 variante posibile pentru ​Block 1, putem asocia encripția corectă pentru un byte, folosind oracolulPresupunem că am găsit ​byte-ul "​w"​. ​Repetăm același proces pentru următorul ​byte
 <​code>​ <​code>​
  Block 1          Block 2  Block 3  Block 1          Block 2  Block 3
Line 64: Line 402:
  ​|----known----|  ​|----known----|
 </​code>​ </​code>​
-We can repeat this process for each byte until we have the whole first block m1, which let's say is "we attack at daw"Unfortunately at this point we can't reduce ​m0 by any more bytes since m0 would be bytes and we would simply get:+Repetând același proces, vom afla, rând pe rând, fiecare ​byte din secretul ​m1, găsind mesajul “we attack at dawDin păcate, în acest punct nu putem ajunge la mesajul m2. Dacă am alege m0 de lungime ​0, obținem:
 <​code>​ <​code>​
  ​M1 ​              M2  ​M1 ​              M2
Line 70: Line 408:
  ​|----known-----|  ​|----known-----|
 </​code>​ </​code>​
-But we since we now know all of m1 we can use the sort of attack we used to recover the first byte of m1 to recover the first byte of m2Suppose we again choose ​m0 to be of length ​15 bytes:+Totuși ne putem folosi de m1 pentru a continua ataculDacă alegem ​m0 de lungime ​15 bytes, vom avea următoarea situatie:
 <​code>​ <​code>​
  Block 1          Block 2          Block 3  Block 1          Block 2          Block 3
Line 76: Line 414:
  ​|------------known-------------|  ​|------------known-------------|
 </​code>​ </​code>​
-There'only one unknown ​byte in Block 2 so all we have to do is again submit all 256 guesses to the encryption oracleexcept this time for Block 2 instead of Block 1! This process can be repeated to decrypt an arbitrary amount of ciphertext that is ECB encrypted as long as we can prepend data to the plaintext and have access to an encryption oracle.+Se poate observa că putem să repetăm acum fix același proces pentru a afla byte-ul necunoscut din Block 2. Cât timp avem un oracol care să valideze atacul brute forceputem repeta procesul pentru a afla oricâți bytes din secret. 
  
 ==== 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 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. 
 + 
 +    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.
  
-def pkcs7_padding(message,​ block_size):+    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 105: 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: 
 +        bytesA 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 117: 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: 
 +        bytesA 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 lengthYou 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, ​sizeOfTheFixedPrefixPlusTargetminimumSizeToAlighPlaintext+        ​# 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, sizeOfTheFixedPrefixPlusTarget,​ 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(sizeOfTheFixedPrefixPlusTarget)) +        - 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 = sizeOfTheFixedPrefixPlusTarget - \ 
-    minimumSizeToAlignPlaintext - prefix_size 
  
-print("\nTarget:") +if __name__ == "​__main__": 
-recoverOneByteAtATime(block_size, prefix_size,​ target_size)+    main()
 </​file>​ </​file>​
 +</​spoiler>​
 +</​hidden>​
ic/labs/06.1604657788.txt.gz · Last modified: 2020/11/06 12:16 by acosmin.maria
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0