hw1 notebook

This commit is contained in:
Hladu357 2025-03-07 12:39:14 +02:00
parent 8a5c08d85c
commit 0f28009197
1 changed files with 720 additions and 0 deletions

720
hw1/hw1.ipynb Normal file
View File

@ -0,0 +1,720 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "660c0928-a36c-43b1-8e1c-46cfea272d71",
"metadata": {},
"source": [
"## Homework 1\n",
"Ondřej Hladůvka\n",
"\n",
"If you want to run this code yourself, you can found original notebook here:\n",
"https://git.hladu.xyz/hladu357/TalTech_crypt/src/branch/master/hw1"
]
},
{
"cell_type": "markdown",
"id": "e67bab4c-3578-47df-8fab-50c8c0ba9703",
"metadata": {},
"source": [
"### Task 1\n",
"Assume that the Affine cipher is implemented in Z97, not in Z26. (Imagine that we just extended\n",
"alphabet, added a set of special symbols. But the first 26 letters stay the same as in English alphabet.)"
]
},
{
"cell_type": "markdown",
"id": "28810138-4e3e-4a37-91af-5514b0770d4a",
"metadata": {},
"source": [
"1. Write down encryption and decryption functions for this modification of Affine cipher."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "93b6d070-f162-4d4a-85df-b3617214343e",
"metadata": {},
"outputs": [],
"source": [
"#For modulo 26:\n",
"#Enc(pt_char) = a · pt_char + b( mod 26)\n",
"#Dec(ct_char) = a^1 · (ct_char b)( mod 26)\n",
"#where a^1 is multiplicative inverse of a modulo 26\n",
"\n",
"# for other modulos we just need to find multiplicative inverses in it\n",
"import math\n",
"\n",
"# multiplicative inverse of a modulo 97\n",
"def inverse_mod97(i : int):\n",
" return pow(i, -1, 97)\n",
"\n",
"def encrypt_mod97(pt_char : int, a_k : int, b_k : int):\n",
" return (a_k * pt_char + b_k) % 97\n",
"\n",
"def decrypt_mod97(ct_char : int, a_k : int, b_k : int):\n",
" return (inverse_mod97(a_k) * (ct_char - b)) % 97"
]
},
{
"cell_type": "markdown",
"id": "b98f3507-8326-4d6e-a7a6-80b43e3a75ca",
"metadata": {},
"source": [
"2. What is the number of possible keys?"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9307241e-be54-4543-b436-79a40892836f",
"metadata": {},
"outputs": [],
"source": [
"from math import gcd\n",
"\n",
"# eulers totient function\n",
"def euler(n : int):\n",
" amount = 0 \n",
" for k in range(1, n + 1):\n",
" if gcd(n, k) == 1:\n",
" amount += 1\n",
" return amount\n",
"\n",
"print(\"Possible independent multiplicative key parts:\", euler(97))\n",
"print(\"Possible independent additive key parts:\", 97)\n",
"print(\"All possible keys:\", euler(97) * 97) # combinatorics multiplication principle"
]
},
{
"cell_type": "markdown",
"id": "6291a24b-84af-4976-8d2c-9b15768288b9",
"metadata": {},
"source": [
"3. Suppose that modulus p = 97 is public. Malicious Eve intercepts 3-letter ciphertext c = 28 83 43.\n",
"Assume that Eve also knows corresponding plaintext m = D O G. Find out the encryption key,\n",
"decryption key and use it to decrypt message c\n",
"\n",
"= 78 23 33 (The result should be 3-letter airport\n",
"code)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "776a1fa6-3ae3-40b1-9adb-a00e7f3a0a01",
"metadata": {},
"outputs": [],
"source": [
"mod = 97\n",
"ct1 = [28, 83, 43]\n",
"pt1 = [ord(x) - ord('A') for x in ['D', 'O', 'G']] # convert plaintext to ints\n",
"pt2 = \"\"\n",
"ct2 = [78, 23, 33]\n",
"\n",
"# print equations\n",
"for p, c in zip(pt1, ct1):\n",
" print(f\"{c} = a * {p} + b (mod {mod})\")\n",
"print()\n",
"\n",
"print(\"we will subtract the first equation from the second to cancel out b \\n\")\n",
"print(f\"{ct1[1]} - {ct1[0]} = a * {pt1[1]} - (a * {pt1[0]}) (mod {mod})\")\n",
"print()\n",
"\n",
"# calncel out b ##################################\n",
"left = ct1[1] - ct1[0]\n",
"right = pt1[1] - pt1[0]\n",
"\n",
"print(f\"{left} = a * {right} (mod {mod})\")\n",
"print()\n",
"\n",
"# find inverse to get value of a ##################\n",
"right_inv = inverse_mod97(right)\n",
"a = (left * right_inv) % mod\n",
"\n",
"print(f\"multiply by the inverse to get a -> {right}^-1 (mod {mod}) == {right_inv}\")\n",
"print(f\"{a} = {(right * right_inv) % mod} * a (mod {mod})\")\n",
"print(f\"a = {a} \\n\")\n",
"print()\n",
"\n",
"# get value of b ################################\n",
"right = a * pt1[0]\n",
"b = ct1[0] - right\n",
"\n",
"print(\"get b from the first equation\")\n",
"print(f\"{ct1[0]} = {a} * {pt1[0]} + b (mod {mod})\")\n",
"print(f\"{b} = {a * pt1[0] - right} + b (mod {mod})\")\n",
"print(f\"b = {b}\")\n",
"print()\n",
"\n",
"# verify against third equation #################\n",
"print(\"check correctness against third equation\")\n",
"print(f\"{ct1[2]} = {a} * {pt1[2]} + {b} (mod {mod})\")\n",
"print(f\"{ct1[2]} = {a * pt1[2] + b} (mod {mod})\")\n",
"print()\n",
"\n",
"# decrypt ct2 ###################################\n",
"print(\"we can now decrypt pt2:\")\n",
"for ct in ct2:\n",
" pt = decrypt_mod97(ct, a, b)\n",
" pt2 += chr(ord('A') + pt)\n",
" print(f\"Dec({ct}) = {pt} => {chr(ord('A') + pt)}\")\n",
"print()\n",
"\n",
"# resolve airport code #########################\n",
"import requests \n",
"response = requests.get(\"https://airport-data.com/api/ap_info.json?iata=\" + pt2)\n",
"data = response.json()\n",
"if response.status_code == 200:\n",
" if data['icao'] is None:\n",
" print(\"Airport not found\")\n",
" else:\n",
" print(f\"Airport is {data['name']} in {data['country']}\")\n",
"else:\n",
" print(\"api error, cant resolve airport code :(\")"
]
},
{
"cell_type": "markdown",
"id": "a8e5bfda-691c-4c77-bede-76204d72c08a",
"metadata": {},
"source": [
"### Task 2\n",
"This task is on constructing frequency diagrams:\n",
"- Find and write down paragraph of English plaintext. It should be at most 600 letters long."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10edb788-996c-49ee-8293-33566738e7e7",
"metadata": {},
"outputs": [],
"source": [
"plaintext = \"\"\"\n",
"According to all known laws of aviation, there is no way a bee should be able to fly.\n",
"Its wings are too small to get its fat little body off the ground.\n",
"The bee, of course, flies anyway because bees don't care what humans think is impossible.\n",
"Yellow, black. Yellow, black. Yellow, black. Yellow, black.\n",
"Ooh, black and yellow!\n",
"Let's shake it up a little.\n",
"Barry! Breakfast is ready!\n",
"Coming!\n",
"Hang on a second.\n",
"Hello?\n",
"Barry?\n",
"Adam?\n",
"Can you believe this is happening?\n",
"I can't.\n",
"I'll pick you up.\n",
"Looking sharp.\n",
"Use the stairs, Your father paid good money for those.\n",
"Sorry. I'm excited.\n",
"Here's the graduate.\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"id": "ad38acfb-8aa6-469d-b826-b0de46bc1a96",
"metadata": {},
"source": [
"1. Construct frequency diagram for the chosen plaintext."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dff20650-e015-4e1c-b4f9-e172dfd57e43",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"from collections import Counter\n",
"\n",
"# filter just letters and convert to uppercase\n",
"plaintext = ''.join(filter(str.isalpha, plaintext.upper()))\n",
" \n",
"# calculate and plot frequencies with matplotlib\n",
"def freq_plot(text : str) :\n",
" letter_counts = Counter(text)\n",
" letters = sorted(letter_counts.keys())\n",
" frequencies = [letter_counts[letter] for letter in letters]\n",
"\n",
" plt.figure(figsize=(10, 6))\n",
" plt.bar(letters, frequencies)\n",
" plt.xlabel('Letters')\n",
" plt.ylabel('Frequency')\n",
" plt.grid(axis='y')\n",
" plt.show()\n",
"\n",
"print(\"Plaintext frequency diagram:\")\n",
"freq_plot(plaintext)"
]
},
{
"cell_type": "markdown",
"id": "f4f589dd-a7aa-4a23-a29c-2c11fa49b377",
"metadata": {},
"source": [
"2. Encrypt your plaintext with shift cipher, permutation cipher, Vigenère cipher and construct frequency\n",
"diagrams for the corresponding ciphertexts. You may choose any suitable keys for the ciphers."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7e73f44-9300-4494-9c11-3d129637f81a",
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"import string\n",
"\n",
"# shift cipher\n",
"def shift_enc(pt : str, key : int):\n",
" return [chr((ord(char) - ord('A') + key) % 26 + ord('A') ) for char in pt]\n",
"\n",
"# generate random shift\n",
"def keygen_shift():\n",
" return random.randint(1,26)\n",
"\n",
"shift_k = keygen_shift()\n",
"shift_ct = shift_enc(plaintext, shift_k)\n",
"\n",
"print(f\"Using shift cipher with key: {shift_k}\")\n",
"print(f\"we will get ciphertext: {shift_ct[:10]}...\")\n",
"print(f\"with frequency diagram:\")\n",
"freq_plot(shift_ct)\n",
"print(f\"we can clearly see the same patterns just shifted by {shift_k}\\n\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d91d2bcb-d35b-41d9-b4ce-76881dbcc398",
"metadata": {},
"outputs": [],
"source": [
"# permutation cipher\n",
"def perm_enc(pt : str, key : list[int]):\n",
" ret = [\"a\" for i in range(0, len(pt)-1)]\n",
" for from_idx, to_idx in enumerate(key):\n",
" ret[to_idx] = pt[from_idx]\n",
" return ret\n",
"\n",
"# generate random permutation from 0 upto len\n",
"def keygen_perm(len : int):\n",
" ret = [ i for i in range(0, len-1)]\n",
" random.shuffle(ret)\n",
" return ret\n",
"\n",
"perm_k = keygen_perm(len(plaintext))\n",
"perm_ct = perm_enc(plaintext, perm_k)\n",
"\n",
"print(f\"Using permutation cipher with key: {perm_k[:10]}...\")\n",
"print(f\"we will get ciphertext: {perm_ct[:10]}...\")\n",
"print(f\"with frequency diagram:\")\n",
"freq_plot(perm_ct)\n",
"print(f\"we can see there is no change compared to the plaintext diagram as permutation does not create any confusion\\n\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "19b0e732-32de-4577-8e70-97e3ab059c26",
"metadata": {},
"outputs": [],
"source": [
"# vigenere cipher\n",
"def vigenere_enc(pt : str, key : str):\n",
" # convert to ints\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",
"# generate len random letters\n",
"def keygen_vigenere(len : int):\n",
" return ''.join([random.choice(string.ascii_uppercase) for i in range(0, len)])\n",
"\n",
"vig_k = keygen_vigenere(10)\n",
"vig_ct = vigenere_enc(plaintext, vig_k)\n",
"\n",
"print(f\"Using vigenere cipher with key: {vig_k}\")\n",
"print(f\"we will get ciphertext: {vig_ct[:10]}...\")\n",
"print(f\"with frequency diagram:\")\n",
"freq_plot(vig_ct)\n",
"print(f\"we can see no correlation of ctyphertext diagram to plaintext as vigenere creates great confusion\")\n",
"print(f\"as plaintext nad key gets longer vigenere with random key converges to uniform distribution among all letters\")"
]
},
{
"cell_type": "markdown",
"id": "41eda99e-472e-4207-9c9b-ec5516ed6d0a",
"metadata": {},
"source": [
"3. Analyse constructed diagrams and explain which properties help you to identify which encryption\n",
"scheme was used."
]
},
{
"cell_type": "markdown",
"id": "80fa07fa-b128-4466-8b30-27aa505414b2",
"metadata": {},
"source": [
"#### Shift cipher\n",
"- Frequency diagram is shifted but retains the same shape as the plaintext\n",
" \n",
"#### Permutation cipher\n",
"- Frequency diagram is identical to the plaintext\n",
" \n",
"#### Vigenere cipher\n",
"- Frequency diagram is randomized and converges to a uniform distribution"
]
},
{
"cell_type": "markdown",
"id": "03f965fa-e918-47fe-8706-09f6b6a19a80",
"metadata": {},
"source": [
"### Task 3\n",
"Assume you have used a time machine and you are back at Julius Caesars era. Now, you need\n",
"to help Julius Caesar in selecting better ways for sending secret messages to his military units. You may\n",
"select among the ones that have been discussed during this course up to Week 3 included (shift cipher,\n",
"substitution cipher, permutation cipher, affine cipher, Vigenère cipher, OTP). Provide an explanation for your\n",
"choice."
]
},
{
"cell_type": "markdown",
"id": "e93e17d4-776e-4552-af4a-cacfe2bdbdc3",
"metadata": {
"jp-MarkdownHeadingCollapsed": true
},
"source": [
"#### I would recommend Vigenere cipher\n",
"For its:\n",
"- Relative strength against frequancy analysis compared to shift or substitution ciphers\n",
"- Both encryption and decryption can be easily done by hand using vigenere square without\n",
"- Its much more practical than OTP or permutation as key can be of any length\n",
"\n",
"- I would also recommend padding message with random amount of random characters form both start and end, because first and last words can be easily guessed. This would lead to overlaping part of key being compromised\n",
"\n",
"- I would not recommend affine cipher - finding multiplicative inverses in roman numerals seems quite difficult 😄"
]
},
{
"cell_type": "markdown",
"id": "cb11e982-7437-45d5-9339-310a75697977",
"metadata": {},
"source": [
"### Task 4\n",
"Suppose you intercepted the following ciphertext c = 00010010 00000111 11101010. You know\n",
"that a 3-letter word was encrypted using one-time pad (to convert letters to binary strings ASCII table was\n",
"used). Can you bruteforce possible keys and learn the message that was encrypted?"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "60fc029e-b140-4d6f-ae7d-c84fc7b4bf94",
"metadata": {},
"outputs": [],
"source": [
"print(\"We cannot bruteforce the key as there is no correlation between it and ciphertext\")\n",
"print(\"plaintext length is only known variable (assuming it is not padded)\")\n",
"ct = [0b00010010, 0b00000111, 0b11101010]\n",
"print(\"\\nWe even can calculate key that produces any \\\"desired\\\" plaintext of the same length :)\\n\")\n",
"\n",
"def otp_enc(pt: str, key: list[int]) -> list[int]:\n",
" pt_int = [ord(char) for char in pt]\n",
" ct_int = [pt_int[i] ^ key[i] for i in range(len(pt_int))]\n",
" return ct_int\n",
"\n",
"def otp_dec(ct: list[int], key: list[int]) -> str:\n",
" pt_int = [ct[i] ^ key[i] for i in range(len(ct))]\n",
" pt = ''.join([chr(i) for i in pt_int])\n",
" return pt\n",
"\n",
"def otp_desired_key(ct: list[int], pt: str) -> list[int]:\n",
" pt_int = [ord(char) for char in pt]\n",
" return [ct[i] ^ pt_int[i] for i in range(len(ct))]\n",
"\n",
"print(\"For example:\")\n",
"pt_a = [\"cat\", \"map\", \"lab\"]\n",
"for pt in pt_a:\n",
" print(f'If we want to get plaintext \\\"{pt}\\\"')\n",
" key = otp_desired_key(ct, pt)\n",
" print(f'We can choose key = {key}')\n",
" ct_i = [bin(i) for i in otp_enc(pt, key)]\n",
" print(f\"Enc({pt}, {key}) = {ct_i}\\n\")"
]
},
{
"cell_type": "markdown",
"id": "96de89f2-333e-44da-8344-6f6803859115",
"metadata": {},
"source": [
"## Task 5\n",
"You have intercepted the following ciphertext encrypted using Vigenere cipher. You have a cryptoanalyst friend who can help you break the cipher, but they asked you to find key length."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "720bab46-d467-491f-8715-cbd139ddb49b",
"metadata": {},
"outputs": [],
"source": [
"ct = \"FHKOJASZAFUDTBJQLVMKFHKZKFWGACXWGGUMNGAVKSNWEWWNMPANKWHFHKUIXIJMFEUJLGZLEBJDOAOJMDUWTKOAEGDEZZAUNMBQAPKVPQAXTATEGLNQSYVKOKCIUMLCIAEHGXRKTUXQUNZVGIGXGHRITLQDSOVVTEXQITTJQTQCZQQZBABRQEBMUFHKXQXTKZIQIYBYMSCWTFHZEQXOISGPDUWTEATLCFROKMETGQTOAYMKRYUCOQTNQOIHKVAAUCMTQLGBGROXKNMSYPGIOATFPRUXYMSZMRMPKZDMSQMVEOTGQGRNMCPPATNDUMAHDOSCPPEXGQGRLMGFPKTVKOAEKFHHQVEOLKJMLQWTENKIMGPHMJUNJGQGITDKEIHTGSRGJAAUXVQEEGVFECXMGOHMWVKOAZEANQ\"\n",
"# freq_plot(ct)\n",
"\n",
"print(\"According to Historical-Ciphers.pdf:26\")\n",
"print()\n",
"print(\"\\\"If there are similar groups of (at least 3) letters in the ciphertext\")\n",
"print(\"Then the most probable explanation is that they correspond to\")\n",
"print(\"similar groups of letters in the plaintext Hence,\")\n",
"print(\"the difference in their positions in the text is divisible by m\\\"\")\n",
"print()\n",
"\n",
"def most_common_ngrams(text : str, n : int):\n",
" ngrams = [text[i:i+n] for i in range(len(text) - n + 1)] # split text to all n-grams\n",
" ngrams = Counter(ngrams).most_common()\n",
" most_repetitions = ngrams[0][1]\n",
" if most_repetitions > 1: # if there are some repeating\n",
" # return ones withh most repetitions\n",
" return list(filter( lambda a : a[1] == most_repetitions, ngrams))\n",
"\n",
"ngrams = []\n",
"print(\"So i found that most common repeating n-grams are:\")\n",
"for i in range(3, 6):\n",
" ngrams.append(most_common_ngrams(ct, i))\n",
" print(f\"{i}-grams: {ngrams[-1:][0]}\")\n",
"\n",
"def substrings_indexes(s : str, sub : str):\n",
" return [i for i in range(len(s)) if s.startswith(sub, i)]\n",
"\n",
"def common_dividors(numbers: list[int]):\n",
" ret = []\n",
" for i in range(2, min(numbers) + 1):\n",
" if all(num % i == 0 for num in numbers): # Check if i divides all numbers\n",
" ret.append(i)\n",
" return ret\n",
"\n",
"print(f\"\\nWe will try first 3-grams, as {ngrams[0][0][0]} is repeating {ngrams[0][0][1]} times\")\n",
"idxs = substrings_indexes(ct, ngrams[0][0][0])\n",
"print(f\"they are at indexes {idxs}\")\n",
"\n",
"spacing = []\n",
"for i in range(1, len(idxs)):\n",
" spacing.append(idxs[i] - idxs[i - 1])\n",
"divisors = common_dividors(spacing)\n",
"\n",
"print(f\"so theyre relative spacing is {spacing}\")\n",
"print(f\"with only common divisor {divisors}\")\n",
"print(f\"its highly probable that key is {divisors} long, but lets look at the 4-grams to be sure\\n\")\n",
"\n",
"for i in range(0, 4):\n",
" idxs = substrings_indexes(ct, ngrams[1][i][0])\n",
" spacing = []\n",
" for i in range(1, len(idxs)):\n",
" spacing.append(idxs[i] - idxs[i - 1])\n",
" \n",
" divisors = common_dividors(spacing)\n",
" print(f\"Instances of {ngrams[1][i][0]} are at indexes {idxs}\")\n",
" print(f\"so theyre relative spacing is {spacing} with divisors {divisors}\\n\")\n",
"\n",
"key_len = 5\n",
"print(f\"All of the them have relative spacing divisible by {key_len}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0571b097-c989-4a74-9dea-21a794a13490",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"def ic(a : str):\n",
" a_c = Counter(list(a))\n",
"\n",
" sum_l = 0\n",
" for i in string.ascii_uppercase:\n",
" sum_l += (a_c[i]/ len(a)) * ((a_c[i] - 1)/(len(a) - 1))\n",
" return sum_l\n",
" \n",
"for key_len in range(2, 11):\n",
" pts = [\"\"] * key_len\n",
" for idx, i in enumerate(ct):\n",
" pts[idx % key_len] += i\n",
" sum_l = 0\n",
" for i in pts:\n",
" sum_l += ic(i)\n",
" \n",
" print(f\"For key length {key_len}, average ic is {(sum_l / key_len):.3f}\")\n",
"\n",
"print()\n",
"print(\"Indices of coincidence being much closer to 0.065 (of english text) for 5 and 10 partial texts\")\n",
"print(\"supports that key should be multiple of five long\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3c37a9a-ee50-4550-869b-dbf49a244f79",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"print(\"Assuming 5 long key, we can now express relations o partial keys as system of linear equations:\")\n",
"\n",
"def mutual_ic(a : str, b : str):\n",
" a_c = Counter(list(a))\n",
" b_c = Counter(list(b))\n",
"\n",
" sum_l = 0\n",
" for i in string.ascii_uppercase:\n",
" sum_l += (a_c[i]/ len(a)) * (b_c[i]/len(b))\n",
" return sum_l\n",
"\n",
"def vigenere_dec(ct : str, key : str):\n",
" ct_int = [ord(i) - ord('A') for i in ct]\n",
" key_int = [ord(i) - ord('A') for i in key]\n",
"\n",
" pt_int = [(char - key_int[idx % len(key_int)]) % 26 for idx, char in enumerate(ct_int)]\n",
" pt = [chr(i + ord('A')) for i in pt_int]\n",
" return pt\n",
"\n",
"\n",
"# According to Historical-Ciphers.pdf:35\n",
"print(\"By measurring spikes in mutual index of coincidence between partial raw ciphertexts\")\n",
"print(\"and other randomly decrypted parts. We can spot how they relate and obtain:\")\n",
"\n",
"pts = [\"\"] * key_len\n",
"for idx, i in enumerate(ct):\n",
" pts[idx % key_len] += i\n",
"cong = []\n",
"for i in range(0, key_len):\n",
" k = None\n",
" for j in range(i+1, key_len):\n",
" for g in string.ascii_uppercase:\n",
" tmp = mutual_ic( pts[i], vigenere_dec(pts[j], str(g)) )\n",
" if tmp > 0.059: # 0.059523809523809005852\n",
" char = ord(g) - ord('A')\n",
" print(f\"{chr(ord('A') + j)} - {chr(ord('A') + i)} == {char} (mod 26)\")\n",
"\n",
"print(\"and solve it as system of liner equations in Z26\")\n",
"print(\"resulting in key = [i, i + 14, i + 20, i + 22, i + 16] with i in {0..25}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "335aaa6c-709b-4354-a0ee-d785cf9b4e99",
"metadata": {},
"outputs": [],
"source": [
"print(\"now we can try spot meaningful text in every i:\")\n",
"key = None\n",
"for i in range(0, 26):\n",
" key = [i, i + 14, i + 20, i + 22, i + 16]\n",
" key = ''.join([chr(i % 26 + ord('A')) for i in key])\n",
" pt = ''.join(vigenere_dec(ct, key)[:15])\n",
" print(f\"for key: {key} -> {pt}\")\n",
" if i == 12:\n",
" print(\"and here it is ^^^^^^^^^^^^^^^\")\n",
" break"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ae19e142-6467-4c62-861c-32fa66365b3e",
"metadata": {},
"outputs": [],
"source": [
"print(f\"Finally, key \\\"{key}\\\" gives us whole plaintext:\")\n",
"print(''.join(vigenere_dec(ct, key)))"
]
},
{
"cell_type": "markdown",
"id": "a142b9b7-74c2-448e-a03e-0814f6e40f46",
"metadata": {},
"source": [
"### Bonus Task 1\n",
"Suppose you encrypt m using an Vigenere cipher of keylength 3 and then encrypt the result\n",
"using Vigenere cipher with different key of length 5. Is there any advantage of doing this, rather than using\n",
"a single encryption function? Why or why not?"
]
},
{
"cell_type": "markdown",
"id": "af0133f5-8013-4a6a-95e3-d8eb8c9be65b",
"metadata": {},
"source": [
"we will get the same ciphertext as if we encrypted by combined key:\n",
"- combined keys is lcm(|k_a|, |k_b|) long, so in this case 15\n",
"- but has entropy only of Ent(k_a) * Ent(k_b) long key, thanks to logharithim nature of entrophy its Ent(|k_a| + |k_b|), which is the same as 8 long key\n",
"- shorter repeating patterns in key would be propably very noticable in Kasiski Examination compared to \"stronger\" 15 long keys\n",
"- there is also potencial risk of keys completly negating themself in upto min(|k_a|, |k_b|) characters - this happens when k_a_i + k_b_j == 0 (mod 26)"
]
},
{
"cell_type": "markdown",
"id": "95297c11-f505-4cb4-81f8-b6a66a46b27c",
"metadata": {},
"source": [
"### Bonus Task 2\n",
"Assuming that the rate of English language is 1.8, find unicity distance of affine cipher."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e664cdbe-a3dc-4637-a3e6-48a4e10589e8",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"\n",
"num_keys = euler(26) * 26\n",
"print(f\"number of keys is euler(26) * 26 = {num_keys}\")\n",
"\n",
"k = math.log2(num_keys)\n",
"print(f\"k = log_2(num_keys) = {k:.2f}\")\n",
"\n",
"rate = 1.8\n",
"n_0 = k / rate\n",
"print(f\"unicity distance = k / rate = {n_0:.2f}\")"
]
}
],
"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
}