This commit is contained in:
Ondrej Hladuvka 2025-05-21 21:12:09 +03:00
parent a7a984c0d8
commit e5d8cd6214
2 changed files with 552 additions and 0 deletions

547
hw2/Homework 2.ipynb Normal file
View File

@ -0,0 +1,547 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "421d5e4d-1327-449b-8aae-16cb1c99cb60",
"metadata": {},
"source": [
"Ondřej Hladůvka \n",
"\n",
"If you want to run code in this notebook, you can find it here: https://git.hladu.xyz/hladu357/TalTech_crypt/src/branch/master/hw2"
]
},
{
"cell_type": "markdown",
"id": "0ed8045c-d080-4288-909a-9190433d6a09",
"metadata": {},
"source": [
"## Task 1 Confusion and diffusion\n",
"From the lectures, you learned about importance of confusion and diffusion principles for the block ciphers. Let us examine them in more details. Assume you need to analyse\n",
"properties of Vigenere cipher."
]
},
{
"cell_type": "markdown",
"id": "c8eedd35-16bd-433d-b161-b1e9c7a26c19",
"metadata": {},
"source": [
"1. Encrypt message THEWANDCHOOSESTHEWIZARD with key MAGIC."
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "47aba105-8f9d-40eb-a5ed-5f93bbe63b86",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ciphertext: FHKECZDIPQASKAVTECQBMRJ\n"
]
}
],
"source": [
"def vigenere_enc(pt : str, key : str):\n",
" pt_int = [ord(i) - ord('A') for i in pt]\n",
" key_int = [ord(i) - ord('A') for i in key]\n",
"\n",
" ct_int = [(char + key_int[idx % len(key_int)]) % 26 for idx, char in enumerate(pt_int)]\n",
" ct = [chr(i + ord('A')) for i in ct_int]\n",
" return ct\n",
"\n",
"pt = \"THEWANDCHOOSESTHEWIZARD\"\n",
"key = \"MAGIC\"\n",
"ct = vigenere_enc(pt, key)\n",
"\n",
"print(\"ciphertext: \", \"\".join(ct))"
]
},
{
"cell_type": "markdown",
"id": "60d515f1-5951-4743-8286-4b41018fabc4",
"metadata": {},
"source": [
"2. Suppose we change one letter (W becomes L) in the plaintext to get THEWANDCHOOSESTHE L IZARD.\n",
"How may letters of the ciphertext are changed? Is the diffusion property achieved?"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "3c55e9ad-dd9f-48ee-b4fe-064dacb0706a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Witch changed plaintext at index 17\n",
"New ciphertext is: FHKECZDIPQASKAVTERQBMRJ\n",
"character changes at index 17 C -> R\n",
"diffusion is not achieved, as the plaintext's change effects the ciphertext in structured way exploitable by cryptanalysis\n"
]
}
],
"source": [
"idx = 17\n",
"pt2 = pt[:idx] + \"L\" + pt[(idx + 1):]\n",
"ct2 = \"\".join(vigenere_enc(pt2, key))\n",
"\n",
"print(f\"Witch changed plaintext at index {idx}\")\n",
"print(f\"New ciphertext is: {ct2}\")\n",
"\n",
"for idx, (a, b) in enumerate(zip(ct, ct2)):\n",
" if (a != b):\n",
" print(f\"character changes at index {idx} {a} -> {b}\")\n",
"print(\"diffusion is not achieved, as the plaintext's change effects the ciphertext in structured way exploitable by cryptanalysis\")"
]
},
{
"cell_type": "markdown",
"id": "47b5e048-9f0a-4436-8294-fcba9fca489e",
"metadata": {},
"source": [
"3. Suppose we change one letter in key (G becomes N). How may letters of the ciphertext are changed?\n",
"Is confusion property achieved?"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "c89a36d4-6c24-4ca3-84c5-bafea87073c3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"it would change lower whole part n / k letters where n is length of plaintext and k is length of the key\n",
"floor(n / k) = floor(23 / 5) = 4\n",
"confusion is not achieved, as the key's effect on the ciphertext is structured and exploitable by cryptanalysis\n"
]
}
],
"source": [
"import math\n",
"print(\"it would change lower whole part n / k letters where n is length of plaintext and k is length of the key\")\n",
"n = len(pt)\n",
"k = len(key)\n",
"print(f\"floor(n / k) = floor({n} / {k}) = {math.floor(n/k)}\")\n",
"print(\"confusion is not achieved, as the key's effect on the ciphertext is structured and exploitable by cryptanalysis\")"
]
},
{
"cell_type": "markdown",
"id": "1b4e0abb-fbcb-4019-9c4d-c8486d7c3884",
"metadata": {},
"source": [
"## Task 2. Pseudorandom function \n",
"Let F {0, 1}\n",
"n × {0, 1}\n",
"n → {0, 1}\n",
"n\n",
"be a secure PRF. Do the following\n",
"functions satisfy definition of pseudo-random function?"
]
},
{
"cell_type": "markdown",
"id": "3dc36d9b-46b1-4222-bd16-19b68356dd6a",
"metadata": {},
"source": [
"1. F(k, m) = F(k, m) || 0^n, where 0^n is a zero string of length n"
]
},
{
"cell_type": "code",
"execution_count": 34,
"id": "bd6cd25a-944c-4844-a82d-601c27a17a80",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Adversary with strategy that guesses its not oraculum if last n bits of the message are all zeros\n",
"can be wrong only if oraculum, was used (1/2 chance) and it generated last last n bits of the message all zeros (2^-n)\n",
"Chance of this is 2 to the power of minus (message length + 1)\n",
"With growing n, this function converges to 0 -> F' is not secure PRF\n",
"\n",
"\n",
"We can simulate this, assumming python random is close enough to random oraculum\n",
"For message 4 characters long, its only a 0.0625 chance\n",
"but only if random oracle is selected so times 0.5 -> 0.03125 chance\n",
"adverasry got response: [1, 1, 1, 1, 0, 0, 0, 0]\n",
"he guessed it is not oraculum - its False\n",
"\n",
"adverasry got response: [0, 1, 0, 0, 0, 0, 0, 0]\n",
"he guessed it is not oraculum - its True\n",
"\n",
"adverasry got response: [0, 0, 1, 0, 0, 0, 0, 0]\n",
"he guessed it is not oraculum - its True\n",
"\n",
"adverasry got response: [1, 1, 1, 0, 0, 0, 0, 0]\n",
"he guessed it is not oraculum - its True\n",
"\n",
"adverasry got response: [0, 0, 0, 1, 0, 0, 0, 0]\n",
"he guessed it is not oraculum - its True\n",
"\n",
"adverasry got response: [1, 1, 0, 0, 0, 1, 0, 0]\n",
"he guessed it is oraculum - its True\n",
"\n",
"we can meassure the prediction:\n",
"adversary passed 971 out of 1000 tests, which is 0.029 chance of failure\n"
]
}
],
"source": [
"# no efficient algorithm can distinguish (with significant advantage)\n",
"# between a function chosen randomly from the PRF family and a random oracle\n",
"import random\n",
"import string\n",
"msg_len = 4\n",
"\n",
"def prf1(input):\n",
" return [random.randint(0, 1) for _ in range(len(input))] + [0] * len(input)\n",
"\n",
"def challenger1(input):\n",
" if (random.choice([True, False])): # random oracle\n",
" return [random.randint(0, 1) for _ in range(len(input) * 2)], True\n",
" else: # tested function\n",
" return prf1(input), False\n",
"\n",
"words = [\"abcd\", \"efgh\", \"ijkl\", \"mnop\", \"qrst\", \"uvwx\"]\n",
"def adversary1(w, verbose=True):\n",
" response = challenger1(w)\n",
" if verbose: print(f\"adverasry got response: {response[0]}\")\n",
" \n",
" if all(x == 0 for x in response[0][len(w):]):\n",
" if verbose: print(f\"he guessed it is not oraculum - its {response[1] == False}\\n\")\n",
" return response[1] == False\n",
" else:\n",
" if verbose: print(f\"he guessed it is oraculum - its {response[1] == True}\\n\")\n",
" return response[1] == True\n",
"\n",
"\n",
"print(\"Adversary with strategy that guesses its not oraculum if last n bits of the message are all zeros\")\n",
"print(\"can be wrong only if oraculum, was used (1/2 chance) and it generated last last n bits of the message all zeros (2^-n)\")\n",
"print(\"Chance of this is 2 to the power of minus (message length + 1)\")\n",
"print(\"With growing n, this function converges to 0 -> F' is not secure PRF\\n\\n\")\n",
"\n",
"chance = pow(2,-msg_len)\n",
"print(f\"We can simulate this, assumming python random is close enough to random oraculum\")\n",
"print(f\"For message {msg_len} characters long, its only a {chance} chance\")\n",
"print(f\"but only if random oracle is selected so times 0.5 -> {chance/2} chance\")\n",
"\n",
"for w in words:\n",
" adversary1(w)\n",
"\n",
"tests = 1000\n",
"passed = 0\n",
"print(f\"we can meassure the prediction:\")\n",
"for i in range(0, tests):\n",
" msg = ''.join(random.choices(string.ascii_lowercase, k=msg_len))\n",
" passed += adversary1(msg, False)\n",
"\n",
"print(f\"adversary passed {passed} out of {tests} tests, which is {round(1 - (passed / tests),5)} chance of failure\")"
]
},
{
"cell_type": "markdown",
"id": "06d4358a-0216-457a-ab08-3662f1999b89",
"metadata": {},
"source": [
"2. F(k, mm) = F(k, m)F(k, m ⊕ 0^n), where 0^n is a zero string of length n"
]
},
{
"cell_type": "markdown",
"id": "f99e71fa-a4e2-4396-8072-9621b40700fc",
"metadata": {},
"source": [
"Since x ⊕ 0 = x, we can simplify\n",
"F' := F(k, mm) = F'(k, m)F'(k, m)\n",
"\n",
"With startegy: \n",
"choose message x\n",
"x = a^n (where n is power of 2)\n",
"\n",
"get message y \n",
"if message is repeating bit => adversary guess its not oraculum \n",
"y = F'(a^(n/2) || F'(a^(n/2)) = F'(a^(n/4)) || F'(a^(n/4)) || F'(a^(n/4)) || F'(a^(n/4)) = ... = F'(a)^n\n",
"\n",
"else => guess its oraculum\n",
"\n",
"Only way adversary could be wrong if oraculum generated n repeating bits, chance of this is 2^(-n) \n",
"With growing n, this function converges to 0 -> F' is not secure PRF"
]
},
{
"cell_type": "markdown",
"id": "fc89bafe-990c-4a05-9bd4-1c86075f4fcd",
"metadata": {},
"source": [
"3. F(k, mm) = F(k, 0m) ⊕ F(k, m1), where m, m∈ {0, 1}^(n1)"
]
},
{
"cell_type": "markdown",
"id": "7fccba29-9283-448a-83e5-0a9e1dca8864",
"metadata": {},
"source": [
"With startegy:\n",
"querying two messages x_1, x_2 \n",
"x_1 = 0^(n-1)||0^(n-1) \n",
"x_2 = 0^(n-1)||1^(n-1) \n",
" \n",
"get messages y_1, y_2 \n",
"\n",
"\n",
"if y_1 ⊕ y_2 == 0^n => adversary guess its not oraculum \n",
"y_1 ⊕ y_2 = (F(k,0||0^(n1)) ⊕ F(k,0^(n1)||1)) ⊕ (F(k,0||0^(n1)) ⊕ F(k,1^(n1||1)) \n",
"y_1 ⊕ y_2 = F(k,0||1^(n1)) ⊕ F(k,1^(n1||1) \n",
" \n",
"else => guess its oraculum \n",
" \n",
"chance of two random oraculum outputs being inverse (y_1 ⊕ y_2 == 0^n) is 2^(-n) \n",
"With growing n, this function converges to 0 -> F' is not secure PRF"
]
},
{
"cell_type": "markdown",
"id": "e62ff1ca-3ea5-4f08-9b2d-286966f45a7e",
"metadata": {},
"source": [
"## Task 3\n",
"Output feedback mode Consider the following permutation cipher instead of permuting plaintext letters to get ciphertext, you are first required to convert plaintext letters to binary form and next you\n",
"permute bits according to the key. Letter H becomes encrypted to T with key (5, 1, 2, 4, 3). Let us view it as\n",
"block cipher with block length 5 bits."
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "9ca5e5f2-19ae-4d1d-aad1-eecc6b9d07e3",
"metadata": {},
"outputs": [],
"source": [
"def perm_enc(pt: list[int], key: list[int]):\n",
" blocksize = 5\n",
" ct = [0] * blocksize\n",
" for idx in range(0, blocksize):\n",
" ct[idx] = pt[key[idx] - 1]\n",
" return ct\n",
"\n",
"def convert_str(pt: list[int]): # converts to list of binary blocks\n",
" bit_array = []\n",
" for char in pt:\n",
" bits = bin(ord(char) - ord('A'))[2:][-5:].zfill(5)\n",
" bit_array.append([int(bit) for bit in bits])\n",
" return bit_array\n",
"\n",
"def ofb(pt, key, iv, cipher):\n",
" ct = []\n",
" for block in pt:\n",
" iv = cipher(iv, key)\n",
" ct.append([x ^ y for (x,y) in zip(iv, block)]) # encrypted iv into block\n",
" return ct\n",
"\n",
"key = [5, 1, 2, 4, 3]"
]
},
{
"cell_type": "markdown",
"id": "95d3d5f0-26cc-4adf-ba2f-3e19aac52174",
"metadata": {},
"source": [
"1. Encrypt world DOG with key (4, 1, 3, 5, 2) using permutation cipher in OFB mode with iv = 01011. Leave\n",
"result as a binary string."
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "63f150c4-cefc-4288-9568-6c0ed4963d7b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"plaintext: [[0, 0, 0, 1, 1], [0, 1, 1, 1, 0], [0, 0, 1, 1, 0]]\n",
"ciphertext: [[1, 0, 0, 0, 0], [1, 0, 1, 0, 0], [1, 1, 1, 1, 1]]\n"
]
}
],
"source": [
"pt = convert_str(['D', 'O', 'G'])\n",
"key = [4, 1, 3, 5, 2]\n",
"iv = [0, 1, 0, 1, 1]\n",
"ct = ofb(pt, key, iv, perm_enc)\n",
"print(f\"plaintext: {pt}\")\n",
"print(f\"ciphertext: {ct}\")"
]
},
{
"cell_type": "markdown",
"id": "2e92ea0e-ccab-4eb7-bb0d-5f325fcd8e77",
"metadata": {},
"source": [
"2. Flip 5-th bit of received ciphertext (0 becomes 1 and vice versa). Now decrypt modified ciphertext. How\n",
"many bits in the plaintext get changed?"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "ae47fd93-809c-4e03-b565-3d1ebb8f4b42",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"original ciphertext: [[1, 0, 0, 0, 0], [1, 0, 1, 0, 0], [1, 1, 1, 1, 1]]\n",
"modified ciphertext: [[1, 0, 0, 0, 1], [1, 0, 1, 0, 0], [1, 1, 1, 1, 1]]\n",
"original plaintext: [[0, 0, 0, 1, 1], [0, 1, 1, 1, 0], [0, 0, 1, 1, 0]]\n",
"modified plaintext: [[0, 0, 0, 1, 0], [0, 1, 1, 1, 0], [0, 0, 1, 1, 0]]\n",
"one bit also flipped -> no diffusion\n"
]
}
],
"source": [
"import copy\n",
"ct2 = copy.deepcopy(ct)\n",
"ct2[0][4] ^= 1\n",
"pt2 = ofb(ct2, key, iv, perm_enc)\n",
"\n",
"print(f\"original ciphertext: {ct}\")\n",
"print(f\"modified ciphertext: {ct2}\")\n",
"print(f\"original plaintext: {pt}\")\n",
"print(f\"modified plaintext: {pt2}\")\n",
"print(f\"one bit also flipped -> no diffusion\")"
]
},
{
"cell_type": "markdown",
"id": "dd8b801d-8bcc-49dc-8ad1-b0d54ce62959",
"metadata": {},
"source": [
"3. Flip the first bit of the IV iv = 11011, decrypt ciphertext from Step 1 with iv\n",
". How many bits in the\n",
"plaintext get changed?"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "a358aa91-7e8a-4ada-82f0-58fdb7aa30a9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"original plaintext: [[0, 0, 0, 1, 1], [0, 1, 1, 1, 0], [0, 0, 1, 1, 0]]\n",
"modified plaintext: [[0, 1, 0, 1, 1], [0, 1, 1, 1, 1], [0, 0, 1, 0, 0]]\n",
"one bit per block also flipped\n"
]
}
],
"source": [
"iv[0] ^= 1\n",
"pt3 = ofb(ct, key, iv, perm_enc)\n",
"\n",
"print(f\"original plaintext: {pt}\")\n",
"print(f\"modified plaintext: {pt3}\")\n",
"print(f\"one bit per block also flipped\")"
]
},
{
"cell_type": "markdown",
"id": "fc3e1681-f07b-4546-bc30-688753fcbd53",
"metadata": {},
"source": [
"## Task 4\n",
"Consider the encryption of nblock message m = m1m2. . .m_n by some block cipher E in\n",
"CFB mode. Let use denote ciphertext produced by E as c = c1c2. . .cn. Show which information about\n",
"the plaintext can be extracted if we get a collision: ci = cj\n",
", where i ≠ j."
]
},
{
"cell_type": "markdown",
"id": "7134ee9b-fcc0-4d6e-a240-208707160694",
"metadata": {},
"source": [
"#### In OFB mode: \n",
"Ek(iv)^n := Ek( Ek( ... Ek(iv))) n times\n",
"ct_n = pt_n ⊕ Ek(iv)^n\n",
"\n",
"ct_i == ct_j implies => \n",
"Ek(iv)^i == Ek(iv)^j => \n",
"pt_i ⊕ pt_j == Ek(iv)^(i-1) ⊕ Ek(iv)^(j-1)"
]
},
{
"cell_type": "markdown",
"id": "7e65fe31-62e3-4c6c-a7a8-e29ee8dc9e57",
"metadata": {},
"source": [
"## Task 5\n",
"Show that Pigpen cipher defined below is not IND-OT-CPA secure (where adversary is allowed to\n",
"do only one query to the challenger in the IND-CPA game).\n",
"The pigpen cipher uses graphical symbols assigned according to a key in the diagram below1\n",
"(NOTE: Positions\n",
"of the letters in the diagrams are random and not known to adversary):"
]
},
{
"cell_type": "markdown",
"id": "296118e7-4df4-451f-8b27-9a317c04d362",
"metadata": {},
"source": [
"#### Indistinguishability under One-Time Chosen Plaintext Attack definition:\n",
"1. The challenger generates a secret key K\n",
"2. The adversary submits two distinct plaintexts Pt_0, Pt_1 of equal length to the challenger\n",
"3. The challenger selects a random bit b ∈ {0,1}, resulting in ciphertext Ct = Ek(M_b) being sent to the adversary\n",
"4. Based on the Ct, the adversary guess b ∈ {0,1} for the value of b\n",
"5. Scheme is not secure if no adversary has a non-negligible advantage in guessing over 1/2\n",
"\n",
"#### Adversary strategy:\n",
"We can expoit Pigpen cipher being monoalphabetic\n",
"Adversary will send one message with repeating characters and another with each character unique \n",
"M_0 := \"AA\" \n",
"M_1 := \"AB\" \n",
"\n",
"If Ct is some repeating characters => choose 0 \n",
"Else => choose 1 \n",
"\n",
"This strategy will work every time thanks to monoalphabeticity"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

5
hw2/run.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
docker run -p 8888:8888 \
-v "$(pwd)":/work \
jupyter