This is an old revision of the document!
Laboratorul 05 - DES
În acest laborator vom face niște exerciții ce folosesc algoritmul DES și variații ale acestuia discutate la curs.
Prezentarea PowerPoint pentru acest laborator poate fi găsită aici.
Puteți lucra acest laborator folosind și platforma Google Colab, accesând acest link.
Click pentru a vedea utils.py
Click pentru a vedea utils.py
- 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 UTF-8 format.
Returns:
str: The string representation of the byte array or the byte string,
decoded using UTF-8 encoding.
Examples:
>>> bytes_to_string(b'Hello')
'Hello'
>>> bytes_to_string(bytearray(b'IC'))
'IC'
"""
return bytes_data.decode(encoding="utf-8")
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
UTF-8 encoding.
Examples:
>>> string_to_bytes('Hello')
b'Hello'
>>> string_to_bytes('IC')
b'IC'
"""
return string_data.encode(encoding="utf-8")
# 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 UTF-8 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 UTF-8 encoding.
Examples:
>>> b64decode("SGVsbG8=")
'Hello'
>>> b64decode("SUM=")
'IC'
"""
return bytes_to_string(base64.b64decode(string_to_bytes(data)))
Exercițiul 1 (2p)
DESX este definit sub forma DESX( (k1,k2,k3), m) = k1 ⊕ DES(k2, m ⊕ k3).
Construiți un atac împotriva DESX care să ruleze într-un timp proporțional cu 2120.
Exercițiul 2 (3p)
De ce următoarele scheme nu aduc nicio îmbunătățire față de DES? Justificați!
a) c = k1 ⊕ DES(k2, m)
b) c = DES(k2, m ⊕ k1)
Puteți folosi mai multe perechi (mesaj, ciphertext).
Exercițiul 3 (5p)
Scopul acestui exercițiu este de a implementa atacul meet-in-the-middle pe 2DES.
Porniți de la scheletul de laborator (vezi mai jos) care se folosește de biblioteca pycrypto.
Realizați următoarele sarcini:
A. Instalați biblioteca pycrypto
B. Implementați 2DES (1p)
Pornind de la schelet, implementați metodele des2_enc si des2_dec folosind double-DES (2DES).
2DES( (k1,k2), m) = DES(k1, DES(k2, m))
C. Testați 2DES (1p)
Folosiți următoarele ciphertexts
c1 = 'cda98e4b247612e5b088a803b4277710f106beccf3d020ffcc577ddd889e2f32'
c2 = '54826ea0937a2c34d47f4595f3844445520c0995331e5d492f55abcf9d8dfadf'
Decriptați-le folosind cheile:
k1 = 'Smerenie'
k2 = 'Dragoste'
Primul plaintext corespunzator lui c1 este m1='Fericiti cei saraci cu duhul, ca'. Găsiți al doilea plaintext m2 ce îi corespunde lui c2.
În biblioteca Pycrypto, DES primește o cheie pe 8 octeți (64 biți) chiar dacă ar trebui să primeasca numai 7 octeti (56 biti). Ultimul bit din fiecare octet este considerat ca fiind un bit de paritate, dar este ignorat de bibliotecă. Astfel cheia ce se folosește va fi într-adevăr de dimensiunea 56 biți. Pentru simplitate însă, în acest exercițiu vom considera că dimensiunea cheiei este de 64 biți. Astfel putem folosi cheile de 8 caractere de mai sus pentru DES.
Vom considera de asemenea că inițializăm DES-ul cu valorile default (i.e. modul ECB și niciun IV).
Decriptați întregul ciphertext (c1 || c2) cu cheile k1 și k2 folosind 2DES și asigurați-vă că rezultatul dat este mesajul inițial m1||m2.
D. Implementați atacul meet-in-the-middle pentru 2DES (3p)
În ultimul și cel mai important pas vi se dau niște perechi plaintext/ciphertext obținute prin criptarea cu 2DES cu niște chei necunoscute:
m1 = 'Pocainta' (în byte string, i.e. poate fi dat direct ca parametru către pycrypto DES)
c1 = '9f98dbd6fe5f785d' (în hex string, trebuie să îl decodificați mai întâi)
m2 = 'Iertarea'
c2 = '6e266642ef3069c2'
Vi se mai dau și ultimii 6 bytes ale celor 2 chei (care sunt altele decat cele folosite la pașii anteriori):
k1 (last 6 bytes) = 'oIkvH5'
k2 (last 6 bytes) = 'GK4EoU'
Sarcina voastră este să găsiți restul octeților din k1 și k2 aplicând atacul meet-in-the-middle.
Pentru a construi un tabel, recomandăm să folosiți o lista de tupluri unde adăugați perechi de forma (cheie, encripție) ca în următorul exemplu:
tb = []
tb.append(('keyval', 'encval'))
Pentru a sorta tabelul:
tbs = sorted(tb, key=itemgetter(1))
Pentru a realiza căutarea binară în tabel, selectați mai întâi a doua coloană (pentru a căuta după encripție):
tenc = [value for _,value in tbs]
iar apoi folosiți biblioteca bisect (e.g. bisect.bisect_left):
https://docs.python.org/2/library/bisect.html
Scheletul de cod:
- desmitm.py
from utils import *
from operator import itemgetter
import bisect
from Crypto.Cipher import DES
def get_index(a, x):
"""Locate the leftmost value exactly equal to x in list a"""
i = bisect.bisect_left(a, x)
if i != len(a) and a[i] == x:
return i
else:
return -1
def des_enc(k, m):
"""
Encrypt a message m with a key k using DES as follows:
c = DES(k, m)
Args:
m should be a bytestring (i.e. a sequence of characters such as 'Hello'
or '\x02\x04')
k should be a bytestring of length exactly 8 bytes.
Note that for DES the key is given as 8 bytes, where the last bit of
each byte is just a parity bit, giving the actual key of 56 bits, as
expected for DES. The parity bits are ignored.
Return:
The bytestring ciphertext c
"""
d = DES.new(k, DES.MODE_ECB)
c = d.encrypt(m)
return c
def des_dec(k, c):
"""
Decrypt a message c with a key k using DES as follows:
m = DES(k, c)
Args:
c should be a bytestring (i.e. a sequence of characters such as 'Hello'
or '\x02\x04')
k should be a bytestring of length exactly 8 bytes.
Note that for DES the key is given as 8 bytes, where the last bit of
each byte is just a parity bit, giving the actual key of 56 bits, as
expected for DES. The parity bits are ignored.
Return:
The bytestring plaintext m
"""
d = DES.new(k, DES.MODE_ECB)
m = d.decrypt(c)
return m
def des2_enc(k1, k2, m):
# TODO 3.B: implement des2_enc
raise NotImplementedError('Not implemented')
def des2_dec(k1, k2, c):
# TODO 3.B: implement des2_dec
raise NotImplementedError('Not implemented')
def main():
k1 = 'Smerenie'
k2 = 'Dragoste'
m1_given = 'Fericiti cei saraci cu duhul, ca'
c1 = 'cda98e4b247612e5b088a803b4277710f106beccf3d020ffcc577ddd889e2f32'
c2 = '54826ea0937a2c34d47f4595f3844445520c0995331e5d492f55abcf9d8dfadf'
# TODO 3.C: Decrypt c1 and c2 using k1 and k2, and make sure that
# des2_dec(k1, k2, c1 || c2) == m1 || m2
#
# Note: The code to decrypt c1 is already provided below. You **need**
# to decrypt c2 as well.
#
m1 = bytes_to_string(des2_dec(string_to_bytes(k1), string_to_bytes(k2),
bytes.fromhex(c1)))
assert m1 == m1_given, f'Expected "{m1_given}", but got "{m1}"'
print('ciphertext:', c1)
print('plaintext:', m1)
print('plaintext in hexa:', str_2_hex(m1))
# TODO 3.D: run meet-in-the-middle attack for the following plaintext/ciphertext
m1 = 'Pocainta'
c1 = '9f98dbd6fe5f785d'
m2 = 'Iertarea'
c2 = '6e266642ef3069c2'
# NOTE: you only need to search for the first 2 bytes of the each key (i.e.,
# to find out what are the values for each `?`)
k1 = '??oIkvH5'
k2 = '??GK4EoU'
if __name__ == '__main__':
main()