From fa9493592eb5e0f48876ec687412946ea42667a5 Mon Sep 17 00:00:00 2001 From: Hladu357 Date: Fri, 7 Mar 2025 12:39:14 +0200 Subject: [PATCH] hw1 notebook --- hw1/hw1.ipynb | 944 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 944 insertions(+) create mode 100644 hw1/hw1.ipynb diff --git a/hw1/hw1.ipynb b/hw1/hw1.ipynb new file mode 100644 index 0000000..02f53c4 --- /dev/null +++ b/hw1/hw1.ipynb @@ -0,0 +1,944 @@ +{ + "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": 1, + "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": 102, + "id": "9307241e-be54-4543-b436-79a40892836f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Possible independent multiplicative key parts: 96\n", + "Possible independent additive key parts: 97\n", + "All possible keys: 9312\n" + ] + } + ], + "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": 103, + "id": "776a1fa6-3ae3-40b1-9adb-a00e7f3a0a01", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "28 = a * 3 + b (mod 97)\n", + "83 = a * 14 + b (mod 97)\n", + "43 = a * 6 + b (mod 97)\n", + "\n", + "we will subtract the first equation from the second to cancel out b \n", + "\n", + "83 - 28 = a * 14 - (a * 3) (mod 97)\n", + "\n", + "55 = a * 11 (mod 97)\n", + "\n", + "multiply by the inverse to get a -> 11^-1 (mod 97) == 53\n", + "5 = 1 * a (mod 97)\n", + "a = 5 \n", + "\n", + "\n", + "get b from the first equation\n", + "28 = 5 * 3 + b (mod 97)\n", + "13 = 0 + b (mod 97)\n", + "b = 13\n", + "\n", + "check correctness against third equation\n", + "43 = 5 * 6 + 13 (mod 97)\n", + "43 = 43 (mod 97)\n", + "\n", + "we can now decrypt pt2:\n", + "Dec(78) = 13 => N\n", + "Dec(23) = 2 => C\n", + "Dec(33) = 4 => E\n", + "\n", + "Airport is Nice Côte d'Azur Airport in France\n" + ] + } + ], + "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": 104, + "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": 105, + "id": "dff20650-e015-4e1c-b4f9-e172dfd57e43", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Plaintext frequency diagram:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0kAAAIPCAYAAACmD5WFAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAN4RJREFUeJzt3XeYVfW5L/DvAMMg0sQCKk1FLDEay1FJTHQQRSVeC0/isURFTpKToEdFj9E0QXMEzVWjEcs5oejNIRpiSaLXioC9YI25il00FCtVGUeY+4cPc9YEMDjOnj3A5/M8+4/123v2+661ZvbMd36rVNTV1dUFAACAJEmrcjcAAADQkghJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVtyll85MiRGTVqVIOx7bbbLi+88EKSZOnSpTnjjDNy/fXXp6amJoMGDcqVV16Zbt26rXGN5cuXZ/bs2enYsWMqKiqatH8AAGDtUVdXl0WLFmWLLbZIq1arny8qa0hKki996Uu555576pfbtPmflk4//fTcdtttmTx5cjp37pyTTz45Rx55ZB588ME1fv/Zs2enZ8+eTdozAACw9nrzzTfTo0eP1T5f9pDUpk2bdO/efaXxBQsWZNy4cZk0aVIGDBiQJJkwYUJ22GGHPPLII9l7773X6P07duyY5NMN0alTp6ZrHAAAWKssXLgwPXv2rM8Iq1P2kPTSSy9liy22SLt27dK/f/+MHj06vXr1yhNPPJHa2toMHDiw/rXbb799evXqlYcffni1IammpiY1NTX1y4sWLUqSbLDBBtlggw1KuzIAAECLVVtbmyT/8DScsoakvfbaKxMnTsx2222XOXPmZNSoUfn617+e5557LnPnzk3btm3TpUuXBl/TrVu3zJ07d7XvOXr06JXOc0qSu+66K+3bt2/qVQAAANYSH3744Rq9rqKurq6uxL2ssfnz56d379655JJLssEGG2To0KENZoWSZM8990x1dXUuvPDCVb7H388krZhSe/fddx1uBwAA67GFCxdmk002yYIFCz4zG5T9cLuiLl26pF+/fnn55ZdzwAEH5OOPP878+fMbzCbNmzdvlecwrVBVVZWqqqqVxisrK1NZWVmKtgEAgLXAmuaBFnWfpMWLF+eVV17J5ptvnt133z2VlZWZ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9rMNLY+ZJAAAgIJGhaRXX321qfsAAABoERoVkvr27Zvq6ur89re/zdKlS5u6JwAAgLJpVEh68skns/POO2fEiBHp3r17vv/97+exxx5r6t4AAACaXaNC0le+8pVcdtllmT17dsaPH585c+Zkn332yU477ZRLLrkk77zzTlP3CQAA0Cy+0IUb2rRpkyOPPDKTJ0/OhRdemJdffjlnnnlmevbsmeOPPz5z5sxpqj4BAACaxRcKSTNmzMgPf/jDbL755rnkkkty5pln5pVXXsndd9+d2bNn57DDDmuqPgEAAJpFo+6TdMkll2TChAmZOXNmDjnkkFx33XU55JBD0qrVp5lrq622ysSJE9OnT5+m7BUAAKDkGhWSrrrqqpx00kk58cQTs/nmm6/yNZtttlnGjRv3hZoDAABobo0KSS+99NI/fE3btm1zwgknNObtAQAAyqZR5yRNmDAhkydPXml88uTJufbaa79wUwAAAOXSqJA0evTobLLJJiuNb7bZZrngggu+cFMAAADl0qiQNGvWrGy11VYrjffu3TuzZs36wk0BAACUS6NC0mabbZZnn312pfFnnnkmG2+88RduCgAAoFwaFZKOPvro/Nu//VumTp2aZcuWZdmyZbn33ntz6qmn5p//+Z+bukcAAIBm06ir251//vl5/fXXs//++6dNm0/fYvny5Tn++OOdkwQAAKzVGhWS2rZtmxtuuCHnn39+nnnmmWywwQb58pe/nN69ezd1fwAAAM2qUSFphX79+qVfv35N1QsAAEDZNSokLVu2LBMnTsyUKVPy9ttvZ/ny5Q2ev/fee5ukOQAAgObWqJB06qmnZuLEiRk8eHB22mmnVFRUNHVfAAAAZdGokHT99dfn97//fQ455JCm7gcAAKCsGnUJ8LZt26Zv375N3QsAAEDZNSoknXHGGbnssstSV1fX1P0AAACUVaMOt3vggQcyderU3H777fnSl76UysrKBs/fdNNNTdIcAABAc2tUSOrSpUuOOOKIpu4FAACg7BoVkiZMmNDUfQAAALQIjTonKUk++eST3HPPPbnmmmuyaNGiJMns2bOzePHiJmsOAACguTVqJumNN97IQQcdlFmzZqWmpiYHHHBAOnbsmAsvvDA1NTW5+uqrm7pPAACAZtGomaRTTz01e+yxRz744INssMEG9eNHHHFEpkyZ0mTNAQAANLdGzSTdf//9eeihh9K2bdsG43369Mnf/va3JmkMAACgHBo1k7R8+fIsW7ZspfG33norHTt2/MJNAQAAlEujQtKBBx6YX/3qV/XLFRUVWbx4cc4999wccsghTdUbAABAs2vU4XYXX3xxBg0alB133DFLly7NMccck5deeimbbLJJfve73zV1jwAAAM2mUSGpR48eeeaZZ3L99dfn2WefzeLFizNs2LAce+yxDS7kAAAAsLZpVEhKkjZt2uS4445ryl4AAADKrlEh6brrrvvM548//vhGNQMAAFBujQpJp556aoPl2trafPjhh2nbtm3at28vJAEAAGutRl3d7oMPPmjwWLx4cWbOnJl99tnHhRsAAIC1WqNC0qpsu+22GTNmzEqzTAAAAGuTJgtJyacXc5g9e3ZTviUAAECzatQ5SX/6058aLNfV1WXOnDm54oor8rWvfa1JGgMAACiHRoWkww8/vMFyRUVFNt100wwYMCAXX3xxU/QFAABQFo0KScuXL2/qPgAAAFqEJj0nCQAAYG3XqJmkESNGrPFrL7nkksaUAAAAKItGhaSnnnoqTz31VGpra7PddtslSV588cW0bt06u+22W/3rKioqmqZLAACAZtKokHTooYemY8eOufbaa7PRRhsl+fQGs0OHDs3Xv/71nHHGGU3aJAAAQHNp1DlJF198cUaPHl0fkJJko402yi9+8QtXtwMAANZqjQpJCxcuzDvvvLPS+DvvvJNFixZ94aYAAADKpVEh6YgjjsjQoUNz00035a233spbb72VG2+8McOGDcuRRx7Z1D0CAAA0m0adk3T11VfnzDPPzDHHHJPa2tpP36hNmwwbNiy//OUvm7RBAACA5tSokNS+fftceeWV+eUvf5lXXnklSbLNNttkww03bNLmAAAAmtsXupnsnDlzMmfOnGy77bbZcMMNU1dX11R9AQAAlEWjQtJ7772X/fffP/369cshhxySOXPmJEmGDRvm8t8AAMBarVEh6fTTT09lZWVmzZqV9u3b148fddRRueOOO5qsOQAAgObWqHOS7rrrrtx5553p0aNHg/Ftt902b7zxRpM0BgAAUA6NmklasmRJgxmkFd5///1UVVV94aYAAADKpVEh6etf/3quu+66+uWKioosX748F110Uaqrq5usOQAAgObWqMPtLrroouy///6ZMWNGPv7445x11ln561//mvfffz8PPvhgU/cIAADQbBo1k7TTTjvlxRdfzD777JPDDjssS5YsyZFHHpmnnnoq22yzTVP3CAAA0Gw+90xSbW1tDjrooFx99dX5yU9+UoqeAAAAyuZzzyRVVlbm2WefLUUvAAAAZdeow+2OO+64jBs3rql7AQAAKLtGXbjhk08+yfjx43PPPfdk9913z4Ybbtjg+UsuuaRJmgOAlqjP2beVvMbrYwaXvAYAq/a5QtKrr76aPn365Lnnnstuu+2WJHnxxRcbvKaioqLpugMAAGhmnyskbbvttpkzZ06mTp2aJDnqqKNy+eWXp1u3biVpDgAAoLl9rnOS6urqGizffvvtWbJkSZM2BAAAUE6NunDDCn8fmgAAANZ2nyskVVRUrHTOkXOQAACAdcnnOieprq4uJ554YqqqqpIkS5cuzb/+67+udHW7m266qek6BAAAaEafKySdcMIJDZaPO+64Jm0GAACg3D5XSJowYUKp+gAAAGgRGnUzWQCA5lTqG/i6eS9Q9IWubgcAALCuEZIAAAAKhCQAAIACIQkAAKCgrCFp9OjR+ad/+qd07Ngxm222WQ4//PDMnDmzwWuWLl2a4cOHZ+ONN06HDh0yZMiQzJs3r0wdAwAA67qyhqTp06dn+PDheeSRR3L33XentrY2Bx54YJYsWVL/mtNPPz1//vOfM3ny5EyfPj2zZ8/OkUceWcauAQCAdVlZLwF+xx13NFieOHFiNttsszzxxBP5xje+kQULFmTcuHGZNGlSBgwYkOTTezXtsMMOeeSRR7L33nuXo20AAGAd1qLuk7RgwYIkSdeuXZMkTzzxRGprazNw4MD612y//fbp1atXHn744VWGpJqamtTU1NQvL1y4MElSW1ub2traUra/Rqpa15W8RktYTz5V6v1tX7cc6+vPtvUundWtt21eGp+1zuWqvb7uayilNf2er6irqyv9T+AaWL58ef7X//pfmT9/fh544IEkyaRJkzJ06NAGoSdJ9txzz1RXV+fCCy9c6X1GjhyZUaNGrTQ+adKktG/fvjTNAwAALd6HH36YY445JgsWLEinTp1W+7oWM5M0fPjwPPfcc/UBqbHOOeecjBgxon554cKF6dmzZw488MDP3BDNZaeRd5a8xnMjB5W8Bmum1Pvbvm451tefbetdOqtbb9u8ND5rnctVe33d11BKK44y+0da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ABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAQZtyNwAAACv0Ofu2ktd4fczgktdg7WYmCQAAoEBIAgAAKBCSAAAACoQkAACAAiEJAACgQEgCAAAoEJIAAAAKhCQAAIACIQkAAKCgTbkbAACA9V2fs28reY3XxwwueY11hZkkAACAAiEJAACgQEgCAAAoWCtC0tixY9OnT5+0a9cue+21Vx577LFytwQAAKyjWnxIuuGGGzJixIice+65efLJJ7PLLrtk0KBBefvtt8vdGgAAsA5q8SHpkksuyXe/+90MHTo0O+64Y66++uq0b98+48ePL3drAADAOqhFh6SPP/44TzzxRAYOHFg/1qpVqwwcODAPP/xwGTsDAADWVS36Pknvvvtuli1blm7dujUY79atW1544YVVfk1NTU1qamrqlxcsWJAkef/991NbW1u6ZtdQm0+WlLzGe++9p/Z6Unt1ddfX2uvyvlZb7fWhdkv8XCln7XV5X6utdrksWrQoSVJXV/eZr6uo+0evKKPZs2dnyy23zEMPPZT+/fvXj5911lmZPn16Hn300ZW+ZuTIkRk1alRztgkAAKxF3nzzzfTo0WO1z7fomaRNNtkkrVu3zrx58xqMz5s3L927d1/l15xzzjkZMWJE/fLy5cvz/vvvZ+ONN05FRUVJ+21qCxcuTM+ePfPmm2+mU6dOaqutttpqr2V11VZbbbXXpbrlrt0U6urqsmjRomyxxRaf+boWHZLatm2b3XffPVOmTMnhhx+e5NPQM2XKlJx88smr/JqqqqpUVVU1GOvSpUuJOy2tTp06le2bUG211VZ7Xaq9Pq6z2mqrve7WXh/XuSl07tz5H76mRYekJBkxYkROOOGE7LHHHtlzzz3zq1/9KkuWLMnQoUPL3RoAALAOavEh6aijjso777yTn//855k7d26+8pWv5I477ljpYg4AAABNocWHpCQ5+eSTV3t43bqsqqoq55577kqHD6qtttpqq7121FVbbbXVXpfqlrt2c2rRV7cDAABobi36ZrIAAADNTUgCAAAoEJIAAAAKhCQAAIACIakFe/jhh9O6desMHjy42WqeeOKJqaioqH9svPHGOeigg/Lss882S/25c+fmlFNOydZbb52qqqr07Nkzhx56aKZMmVKymsV1rqysTLdu3XLAAQdk/PjxWb58ecnqrqp+8XHQQQeVrfbLL79c8tpz587Nqaeemr59+6Zdu3bp1q1bvva1r+Wqq67Khx9+WLK6J554Yv3NqYumTZuWioqKzJ8/v2S116SP5qz3hz/8Ie3atcvFF19clvrNUbOioiL/+q//utJzw4cPT0VFRU488cSS1h4zZkyD8VtuuSUVFRUlqfn33nzzzZx00knZYost0rZt2/Tu3Tunnnpq3nvvvZLWLX6utG3bNn379s15552XTz75pKR1/752ZWVlttpqq5x11llZunRpyWu/8847+cEPfpBevXqlqqoq3bt3z6BBg/Lggw+WtO6qPsOLj5EjR5as9n777ZfTTjttpfGJEyemS5cuJat76KGHrvZ35P3335+Kioom/7vl6quvTseOHRt8Hy9evDiVlZXZb7/9Grx2xe+TV155pUl7WLZsWb761a/myCOPbDC+YMGC9OzZMz/5yU+atF5RXV1dBg4cmEGDBq303JVXXpkuXbrkrbfeKln9chCSWrBx48bllFNOyX333ZfZs2c3W92DDjooc+bMyZw5czJlypS0adMm3/zmN0te9/XXX8/uu++ee++9N7/85S/zl7/8JXfccUeqq6szfPjwktZesc6vv/56br/99lRXV+fUU0/NN7/5zWb5xV7c5isev/vd70ped3W1t9pqq5LWfPXVV7PrrrvmrrvuygUXXJCnnnoqDz/8cM4666zceuutueeee0pan+Q3v/lNjj322Fx11VU544wzyt1OyfTs2TPXX399Pvroo/qxpUuXZtKkSenVq1dJa7dr1y4XXnhhPvjgg5LWWZVXX301e+yxR1566aX87ne/y8svv5yrr746U6ZMSf/+/fP++++XtP6Kz5WXXnopZ5xxRkaOHJlf/vKXJa3597VfffXVXHrppbnmmmty7rnnlrzukCFD8tRTT+Xaa6/Niy++mD/96U/Zb7/9Sh5Ki5/dv/rVr9KpU6cGY2eeeWZJ65fDsGHDcvfdd6/yj/IJEyZkjz32yM4779ykNaurq7N48eLMmDGjfuz+++9P9+7d8+ijjzYI4lOnTk2vXr2yzTbbNGkPrVu3zsSJE3PHHXfkv//7v+vHTznllHTt2rWk3+cVFRWZMGFCHn300VxzzTX146+99lrOOuus/PrXv06PHj1KVr8c1or7JK2PFi9enBtuuCEzZszI3LlzM3HixPz4xz9ultor/gOWJN27d8/ZZ5+dr3/963nnnXey6aablqzuD3/4w1RUVOSxxx7LhhtuWD/+pS99KSeddFLJ6iYN13nLLbfMbrvtlr333jv7779/Jk6cmH/5l39ptvrNrRy1f/jDH6ZNmzaZMWNGg3299dZb57DDDos7E5TWRRddlHPPPTfXX399jjjiiHK3U1K77bZbXnnlldx000059thjkyQ33XRTevXqVfJ/BgwcODAvv/xyRo8enYsuuqiktf7e8OHD07Zt29x1113ZYIMNkiS9evXKrrvumm222SY/+clPctVVV5WsfvFz5Qc/+EFuvvnm/OlPf8o555xTspqrqt2zZ88MHDgwd999dy688MKS1Zw/f37uv//+TJs2Lfvuu2+SpHfv3tlzzz1LVnOF4ud3586dU1FRUbbfJ83lm9/8ZjbddNNMnDgxP/3pT+vHFy9enMmTJ5ckkG+33XbZfPPNM23atOy9995JPp0xOuyww3LvvffmkUceqZ9RmjZtWqqrq5u8hyTp169fxowZk1NOOSUDBgzIY489luuvvz6PP/542rZtW5KaK/Ts2TOXXXZZTj755Bx44IHp06dPhg0blgMPPDDf+c53Slq7HMwktVC///3vs/3222e77bbLcccdl/Hjx5flD8fFixfnt7/9bfr27ZuNN964ZHXef//93HHHHRk+fHiDP5pXKOW0/eoMGDAgu+yyS2666aZmr70ue++993LXXXetdl8nabbDkdZHP/rRj3L++efn1ltvXecD0gonnXRSJkyYUL88fvz4DB06tOR1W7dunQsuuCC//vWvm/UwlPfffz933nlnfvjDH9YHpBW6d++eY489NjfccEOz/k7ZYIMN8vHHHzdbvRWee+65PPTQQyX/47FDhw7p0KFDbrnlltTU1JS0FkmbNm1y/PHHZ+LEiQ2+jydPnpxly5bl6KOPLknd6urqTJ06tX556tSp2W+//bLvvvvWj3/00Ud59NFHSxaSkk9njnbZZZd85zvfyfe+9738/Oc/zy677FKyekUnnHBC9t9//5x00km54oor8txzzzWYWVqXCEkt1Lhx43Lccccl+fTQgQULFmT69OnNUvvWW2+t/8Dv2LFj/vSnP+WGG25Iq1al+3Z5+eWXU1dXl+23375kNRpj++23z+uvv17yOsVtvuJxwQUXlLzuqmp/61vfKmm9Fft6u+22azC+ySab1Pfwox/9qKQ9rGp7H3zwwSWt2RLcfvvtueiii/LHP/4x+++/f7nbaTbHHXdcHnjggbzxxht544038uCDD9Z/vpbaEUccka985SvNcrjXCi+99FLq6uqyww47rPL5HXbYIR988EHeeeedkvdSV1eXe+65J3feeWcGDBhQ8nrJ//x8t2vXLl/+8pfz9ttv59///d9LWrNNmzaZOHFirr322nTp0iVf+9rX8uMf/7jZzuddH5100kl55ZVXGvxtNGHChAwZMiSdO3cuSc3q6uo8+OCD+eSTT7Jo0aI89dRT2XffffONb3wj06ZNS/Lp+eQ1NTUlDUkVFRW56qqrMmXKlHTr1i1nn312yWqtyn/+53/mueeey2mnnZb//M//LOlRRuXkcLsWaObMmXnsscdy8803J/n0w/eoo47KuHHjVjo5sBSqq6vrD8P44IMPcuWVV+bggw/OY489lt69e5ekZks9vKqurq5ZZjWK23yFrl27lrzuqmqvbnan1B577LEsX748xx57bMn/E7uq7f3oo4822x/O5bLzzjvn3Xffzbnnnps999wzHTp0KHdLzWLTTTfN4MGD6//rPHjw4GyyySbNVv/CCy/MgAEDmv3ckHJ+rq4IKrW1tVm+fHmOOeaYkl5AoGjFz/eSJUty6aWXpk2bNhkyZEjJ6w4ZMiSDBw/O/fffn0ceeaT+nxK/+c1vSnaBkPXZ9ttvn69+9asZP3589ttvv7z88su5//77c95555Ws5n777ZclS5bk8ccfzwcffJB+/fpl0003zb777puhQ4dm6dKlmTZtWrbeeuuSn/M4fvz4tG/fPq+99lreeuut9OnTp6T1ijbbbLN8//vfzy233NLsF+RpTmaSWqBx48blk08+yRZbbJE2bdqkTZs2ueqqq3LjjTdmwYIFJa+/4YYbpm/fvunbt2/+6Z/+Kb/5zW+yZMmS/Nd//VfJam677bapqKjICy+8ULIajfH888+X/LyFpOE2X/ForpD097U333zzktbr27dvKioqMnPmzAbjW2+9dfr27bvS4UGlsKrtveWWW5a8brltueWWmTZtWv72t7/loIMOyqJFi8rdUrM56aST6v/TX+pzHP/eN77xjQwaNKhZzsdJ/udn7Pnnn1/l888//3w22mijkv73t7q6Ok8//XReeumlfPTRR7n22mub7R8wK36+d9lll4wfPz6PPvpoxo0b1yy127VrlwMOOCA/+9nP8tBDD+XEE09s1lnE5tapU6dV/l0yf/78ks3mFA0bNiw33nhjFi1alAkTJmSbbbapPyesFPr27ZsePXpk6tSpmTp1an2tLbbYIj179sxDDz2UqVOnlnzW9KGHHsqll16aW2+9NXvuuWeGDRvW7P8UWfH36bpMSGphPvnkk1x33XW5+OKL8/TTT9c/nnnmmWyxxRbNdsWzooqKirRq1arB1aGaWteuXTNo0KCMHTs2S5YsWen55rosc9G9996bv/zlL83yH8j1ycYbb5wDDjggV1xxxSr3NaXVu3fvTJ8+PXPnzl2vgtJBBx2Ujz/+OLW1tau8hG2pjRkzJn/+85/z8MMPl7zWip+xK6+8cqXP7blz5+a///u/c9RRR5V0lnxFUOnVq1dZ/5Bq1apVfvzjH+enP/1pSX+Hrc6OO+64Tn/ObbfddnnyySdXGn/yySfTr1+/ktf/9re/nVatWmXSpEm57rrrctJJJ5X86I/q6upMmzYt06ZNa3B0zze+8Y3cfvvteeyxx0p6qN2HH36YE088MT/4wQ9SXV2dcePG5bHHHsvVV19dsprrKyGphbn11lvzwQcfZNiwYdlpp50aPIYMGdIs/w2rqanJ3LlzM3fu3Dz//PM55ZRTsnjx4hx66KElrTt27NgsW7Yse+65Z2688ca89NJLef7553P55Zenf//+Ja29Yp3/9re/5cknn8wFF1yQww47LN/85jdz/PHHl7R2sX7x8e6775a8brlceeWV+eSTT7LHHnvkhhtuyPPPP5+ZM2fmt7/9bV544YW0bt263C2u03r27Jlp06bl7bffzqBBg7Jw4cJmqbtgwYIG//x5+umn8+abbzZL7datW+f555/P//t//68s319f/vKXc+yxx+byyy9vlnpXXHFFampqMmjQoNx333158803c8cdd+SAAw7Illtumf/4j/9olj5agm9961tp3bp1xo4dW7Ia7733XgYMGJDf/va3efbZZ/Paa69l8uTJueiii3LYYYeVrG65/eAHP8iLL76Yf/u3f8uzzz6bmTNn5pJLLsnvfve7Zrm1QIcOHXLUUUflnHPOyZw5c5rlsMbq6uo88MADefrppxvMWu2777655ppr8vHHH5c0JJ1zzjmpq6urvwdbnz598r//9//OWWed1SznUK9PhKQWZty4cRk4cOAqp6mHDBmSGTNmlPxE0DvuuCObb755Nt988+y11155/PHHM3ny5JKfD7X11lvnySefTHV1dc4444zstNNOOeCAAzJlypSSXqo2+Z917tOnTw466KBMnTo1l19+ef74xz82yx9UxW2+4rHPPvuUvG65bLPNNnnqqacycODAnHPOOdlll12yxx575Ne//nXOPPPMnH/++eVucZ3Xo0ePTJs2Le+++26zBaVp06Zl1113bfAYNWpUyeuu0KlTp3Tq1KnZ6v298847r1luUJ18egjzjBkzsvXWW+fb3/52ttlmm3zve99LdXV1Hn744WY7nLclaNOmTU4++eRcdNFFJZvV6dChQ/baa69ceuml+cY3vpGddtopP/vZz/Ld7343V1xxRUlqtgRbb7117rvvvrzwwgsZOHBg9tprr/z+97/P5MmTm+WG6Mmnh9x98MEHGTRoULbYYouS16uurs5HH32Uvn37plu3bvXj++67bxYtWlR/qfBSmD59esaOHZsJEyakffv29ePf//7389WvfrUsh92tyyrqbE0AAIB6ZpIAAAAKhCQAAIACIQkAAKBASAIAACgQkgAAAAqEJAAAgAIhCQAAoEBIAgAAKBCSAGhRTjzxxBx++OGN+tr99tsvp512WoOxadOmpaKiIvPnz//CvQGwfhCSAGAN1NXV5ZNPPil3GwA0AyEJgLXGc889l4MPPjgdOnRIt27d8p3vfCfvvvtukk9noKZPn57LLrssFRUVqaioyOuvv57q6uokyUYbbZSKioqceOKJSZLly5dn9OjR2WqrrbLBBhtkl112yR/+8If6WitmoG6//fbsvvvuqaqqygMPPJBnnnkm1dXV6dixYzp16pTdd989M2bMaPZtAUDpCEkArBXmz5+fAQMGZNddd82MGTNyxx13ZN68efn2t7+dJLnsssvSv3//fPe7382cOXMyZ86c9OzZMzfeeGOSZObMmZkzZ04uu+yyJMno0aNz3XXX5eqrr85f//rXnH766TnuuOMyffr0BnXPPvvsjBkzJs8//3x23nnnHHvssenRo0cef/zxPPHEEzn77LNTWVnZvBsDgJJqU+4GAGBNXHHFFdl1111zwQUX1I+NHz8+PXv2zIsvvph+/fqlbdu2ad++fbp3717/mq5duyZJNttss3Tp0iVJUlNTkwsuuCD33HNP+vfvnyTZeuut88ADD+Saa67JvvvuW//15513Xg444ID65VmzZuXf//3fs/322ydJtt1225KtMwDlISQBsFZ45plnMnXq1HTo0GGl51555ZX069dvjd/r5Zdfzocfftgg/CTJxx9/nF133bXB2B577NFgecSIEfmXf/mX/J//838ycODAfOtb38o222zzOdYEgJZOSAJgrbB48eIceuihufDCC1d6bvPNN//c75Ukt912W7bccssGz1VVVTVY3nDDDRssjxw5Msccc0xuu+223H777Tn33HNz/fXX54gjjvhcPQDQcglJAKwVdtttt9x4443p06dP2rRZ9a+vtm3bZtmyZSuNJWkwvuOOO6aqqiqzZs1qcGjdmurXr1/69euX008/PUcffXQmTJggJAGsQ1y4AYAWZ8GCBXn66acbPL73ve/l/fffz9FHH53HH388r7zySu68884MHTq0PgD16dMnjz76aF5//fW8++67Wb58eXr37p2Kiorceuuteeedd7J48eJ07NgxZ555Zk4//fRce+21eeWVV/Lkk0/m17/+da699trV9vXRRx/l5JNPzrRp0/LGG2/kwQcfzOOPP54ddtihuTYNAM3ATBIALc60adNWOjdo2LBhefDBB/OjH/0oBx54YGpqatK7d+8cdNBBadXq0//5nXnmmTnhhBOy44475qOPPsprr72WPn36ZNSoUTn77LMzdOjQHH/88Zk4cWLOP//8bLrpphk9enReffXVdOnSJbvttlt+/OMfr7av1q1b57333svxxx+fefPmZZNNNsmRRx6ZUaNGlXR7ANC8Kurq6urK3QQAAEBL4XA7AACAAiEJAACgQEgCAAAoEJIAAAAKhCQAAIACIQkAAKBASAIAACgQkgAAAAqEJAAAgAIhCQAAoEBIAgAAKBCSAAAACv4/NqhgdIG1C/wAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "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": 106, + "id": "d7e73f44-9300-4494-9c11-3d129637f81a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using shift cipher with key: 7\n", + "we will get ciphertext: ['H', 'J', 'J', 'V', 'Y', 'K', 'P', 'U', 'N', 'A']...\n", + "with frequency diagram:\n" + ] + }, + { + "data": { + "image/png": "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gKKa1vUlXb+f5Zaj1O914v0GoPms7++UioakKVOm5JlnnsmTTz651mMLFixI27Zt07Vr10bj3bp1y4IFCz51nWPHjs3555+/1vjvf//7dOjQ4Qv3DPx1l+xd2vX/3//7f0tbgPVW6vc68X4D0Hw+/PDD9XpexULSm2++mVNPPTX33ntv2rVr12zrPeecczJ69OiG5SVLlqRXr1454IAD0rlz52arA3y6XcfcU9L1vzhmWEnXz/or9XudeL9hY2O/QimtOcrsr6lYSHr66afz9ttvZ88992wYW7VqVR544IFcddVVueeee7Jy5cosWrSo0WzSwoUL0717909db01NTWpqatYar66uTnV1dbNuA7ButauqSrp+P8stR6nf68T7DRsb+xVKaX3f+4qFpP322y9/+MMfGo2NGDEi/fv3z49+9KP06tUr1dXVmTZtWoYPH54kmTVrVubOnZuBAwdWomUAAGAjULGQ1KlTp+y6666NxjbZZJNsvvnmDeMjR47M6NGjs9lmm6Vz58455ZRTMnDgwE+9aAMAAMAXVfGr232Wyy+/PK1atcrw4cNTW1ubYcOG5eqrr650WwAAwAasRYWkGTNmNFpu165dxo8fn/Hjx1emIQAAYKNT8ZvJAgAAtCRCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAQZtKNwAA0JL1PfvOkq5/zrhDSrp+4PMzkwQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQ0KbSDWxs+p59Z8lrzBl3SMlrAADAhspMEgAAQIGQBAAAUCAkAQAAFFQ0JF1zzTXZbbfd0rlz53Tu3DkDBw7MXXfd1fD4ihUrMmrUqGy++ebp2LFjhg8fnoULF1awYwAAYENX0ZDUs2fPjBs3Lk8//XSeeuqpDBkyJIcddlj++Mc/JklOP/30/O53v8vUqVMzc+bMzJs3L0ceeWQlWwYAADZwFb263aGHHtpo+Wc/+1muueaaPPbYY+nZs2cmTJiQyZMnZ8iQIUmSiRMnZuedd85jjz2WffbZpxItAwAAG7gWc07SqlWrMmXKlCxfvjwDBw7M008/nbq6ugwdOrThOf3790/v3r3z6KOPVrBTAABgQ1bx+yT94Q9/yMCBA7NixYp07Ngxt912W3bZZZc899xzadu2bbp27dro+d26dcuCBQs+dX21tbWpra1tWF6yZEmSpK6uLnV1dSXZhs+jpnV9yWu0hO1k41bqz7nPeMthn8bGwD6tvOxXKKX1fe+r6uvrS/9J/AwrV67M3Llzs3jx4vzmN7/J9ddfn5kzZ+a5557LiBEjGgWeJNl7770zePDgXHzxxetc35gxY3L++eevNT558uR06NChJNsAAAC0fB9++GGOOeaYLF68OJ07d/7U51U8JP2loUOHZvvtt89RRx2V/fbbLx988EGj2aQ+ffrktNNOy+mnn77O71/XTFKvXr3y7rvvfuYLUS67jrmn5DVeHDOs5DXgs5T6c+4z3nLYp7ExsE8rL/sVSmnJkiXZYost/mpIqvjhdn9p9erVqa2tzYABA1JdXZ1p06Zl+PDhSZJZs2Zl7ty5GThw4Kd+f01NTWpqatYar66uTnV1dcn6Xl+1q6pKXqMlbCcbt1J/zn3GWw77NDYG9mnlZb9CKa3ve1/RkHTOOefkoIMOSu/evbN06dJMnjw5M2bMyD333JMuXbpk5MiRGT16dDbbbLN07tw5p5xySgYOHOjKdgAAQMlUNCS9/fbbOf744zN//vx06dIlu+22W+65557sv//+SZLLL788rVq1yvDhw1NbW5thw4bl6quvrmTLAADABq6iIWnChAmf+Xi7du0yfvz4jB8/vkwdAQAAG7sWc58kAACAlkBIAgAAKBCSAAAAClrcJcABgJap79l3lnT9c8YdUtL1A+tW6p/t5Mv3820mCQAAoKBJIem1115r7j4AAABahCaFpH79+mXw4MH51a9+lRUrVjR3TwAAABXTpJD0zDPPZLfddsvo0aPTvXv3/NM//VOeeOKJ5u4NAACg7JoUkr72ta/liiuuyLx583LDDTdk/vz5+eY3v5ldd901l112Wd55553m7hMAAKAsvtCFG9q0aZMjjzwyU6dOzcUXX5xXX301Z555Znr16pXjjz8+8+fPb64+AQAAyuILhaSnnnoqP/zhD7P11lvnsssuy5lnnpnZs2fn3nvvzbx583LYYYc1V58AAABl0aT7JF122WWZOHFiZs2alYMPPjg33XRTDj744LRq9Unm2nbbbTNp0qT07du3OXsFAAAouSaFpGuuuSYnnXRSTjzxxGy99dbrfM5WW22VCRMmfKHmAAAAyq1JIemVV175q89p27ZtTjjhhKasHgAAoGKadE7SxIkTM3Xq1LXGp06dmhtvvPELNwUAAFApTQpJY8eOzRZbbLHW+FZbbZWLLrroCzcFAABQKU0KSXPnzs2222671nifPn0yd+7cL9wUAABApTQpJG211VZ54YUX1hp//vnns/nmm3/hpgAAACqlSSHp6KOPzr/8y79k+vTpWbVqVVatWpX7778/p556av7u7/6uuXsEAAAomyZd3e7CCy/MnDlzst9++6VNm09WsXr16hx//PHOSQIAAL7UmhSS2rZtm5tvvjkXXnhhnn/++bRv3z5f/epX06dPn+buDwAAoKyaFJLW2HHHHbPjjjs2Vy8AAAAV16SQtGrVqkyaNCnTpk3L22+/ndWrVzd6/P7772+W5gAAAMqtSSHp1FNPzaRJk3LIIYdk1113TVVVVXP3BQAAUBFNCklTpkzJf//3f+fggw9u7n4AAAAqqkmXAG/btm369evX3L0AAABUXJNC0hlnnJErrrgi9fX1zd0PAABARTXpcLuHHnoo06dPz1133ZWvfOUrqa6ubvT4rbfe2izNAQAAlFuTQlLXrl1zxBFHNHcvAAAAFdekkDRx4sTm7gMAAKBFaNI5SUny8ccf57777st1112XpUuXJknmzZuXZcuWNVtzAAAA5dakmaQ33ngjBx54YObOnZva2trsv//+6dSpUy6++OLU1tbm2muvbe4+AQAAyqJJM0mnnnpq9tprr3zwwQdp3759w/gRRxyRadOmNVtzAAAA5dakmaQHH3wwjzzySNq2bdtovG/fvvnzn//cLI0BAABUQpNmklavXp1Vq1atNf7WW2+lU6dOX7gpAACASmlSSDrggAPyi1/8omG5qqoqy5Yty3nnnZeDDz64uXoDAAAouyYdbnfppZdm2LBh2WWXXbJixYocc8wxeeWVV7LFFlvk17/+dXP3CAAAUDZNCkk9e/bM888/nylTpuSFF17IsmXLMnLkyBx77LGNLuQAAADwZdOkkJQkbdq0yXHHHdecvQAAAFRck0LSTTfd9JmPH3/88U1qBgAAoNKaFJJOPfXURst1dXX58MMP07Zt23To0EFIAgAAvrSadHW7Dz74oNHXsmXLMmvWrHzzm9904QYAAOBLrUkhaV122GGHjBs3bq1ZJgAAgC+TZgtJyScXc5g3b15zrhIAAKCsmnRO0m9/+9tGy/X19Zk/f36uuuqqfOMb32iWxgAAACqhSSHp8MMPb7RcVVWVLbfcMkOGDMmll17aHH0BAABURJNC0urVq5u7DwAAgBahWc9JAgAA+LJr0kzS6NGj1/u5l112WVNKAAAAVESTQtKzzz6bZ599NnV1ddlpp52SJC+//HJat26dPffcs+F5VVVVzdMlAABAmTQpJB166KHp1KlTbrzxxmy66aZJPrnB7IgRI/Ktb30rZ5xxRrM2CQAAUC5NOifp0ksvzdixYxsCUpJsuumm+elPf+rqdgAAwJdak0LSkiVL8s4776w1/s4772Tp0qVfuCkAAIBKaVJIOuKIIzJixIjceuuteeutt/LWW2/llltuyciRI3PkkUc2d48AAABl06Rzkq699tqceeaZOeaYY1JXV/fJitq0yciRI/Pzn/+8WRsEAAAopyaFpA4dOuTqq6/Oz3/+88yePTtJsv3222eTTTZp1uYAAADK7QvdTHb+/PmZP39+dthhh2yyySapr69vrr4AAAAqokkh6b333st+++2XHXfcMQcffHDmz5+fJBk5cqTLfwMAAF9qTQpJp59+eqqrqzN37tx06NChYfyoo47K3Xff3WzNAQAAlFuTzkn6/e9/n3vuuSc9e/ZsNL7DDjvkjTfeaJbGAAAAKqFJM0nLly9vNIO0xvvvv5+ampov3BQAAEClNCkkfetb38pNN93UsFxVVZXVq1fnkksuyeDBg5utOQAAgHJr0uF2l1xySfbbb7889dRTWblyZc4666z88Y9/zPvvv5+HH364uXsEAAAomybNJO266655+eWX881vfjOHHXZYli9fniOPPDLPPvtstt9+++buEQAAoGw+90xSXV1dDjzwwFx77bX58Y9/XIqeAAAAKuZzzyRVV1fnhRdeKEUvAAAAFdekw+2OO+64TJgwobl7AQAAqLgmXbjh448/zg033JD77rsvAwYMyCabbNLo8csuu6xZmgMAoPz6nn1nyWvMGXdIyWtAU32ukPTaa6+lb9++efHFF7PnnnsmSV5++eVGz6mqqmq+7gAAAMrsc4WkHXbYIfPnz8/06dOTJEcddVSuvPLKdOvWrSTNAQAAlNvnOiepvr6+0fJdd92V5cuXN2tDAAAAldSkCzes8ZehCQAA4Mvuc4Wkqqqqtc45cg4SAACwIflc5yTV19fnxBNPTE1NTZJkxYoV+ed//ue1rm536623Nl+HAAAAZfS5QtIJJ5zQaPm4445r1mYAAAAq7XOFpIkTJ5aqDwAAgBahSTeTBQAAmo8b+LYsX+jqdgAAABsaIQkAAKBASAIAACgQkgAAAAoqGpLGjh2bv/mbv0mnTp2y1VZb5fDDD8+sWbMaPWfFihUZNWpUNt9883Ts2DHDhw/PwoULK9QxAACwoatoSJo5c2ZGjRqVxx57LPfee2/q6upywAEHZPny5Q3POf300/O73/0uU6dOzcyZMzNv3rwceeSRFewaAADYkFX0EuB33313o+VJkyZlq622ytNPP51vf/vbWbx4cSZMmJDJkydnyJAhST65V9POO++cxx57LPvss08l2gYAADZgLeo+SYsXL06SbLbZZkmSp59+OnV1dRk6dGjDc/r375/evXvn0UcfXWdIqq2tTW1tbcPykiVLkiR1dXWpq6srZfvrpaZ1fclrtITtZONW6s+5z3jLYZ+2cdlYf7Y3xu2u5M/2xrpf8ZqXx/r2UVVfX1/6V2U9rF69Ov/rf/2vLFq0KA899FCSZPLkyRkxYkSj0JMke++9dwYPHpyLL754rfWMGTMm559//lrjkydPTocOHUrTPAAA0OJ9+OGHOeaYY7J48eJ07tz5U5/XYmaSRo0alRdffLEhIDXVOeeck9GjRzcsL1myJL169coBBxzwmS9Euew65p6S13hxzLCS14DPUurPuc94y2GftnHZWH+2N8btruTP9sa6X/Gal8eao8z+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AAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABW0q3QAbh75n31nyGnPGHVLyGgAAbPjMJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUtKl0A7Ah63v2nSVd/5xxh5R0/QAAGyMzSQAAAAVCEgAAQIGQBAAAUPClCEnjx49P3759065du3z961/PE088UemWAACADVSLD0k333xzRo8enfPOOy/PPPNMdt999wwbNixvv/12pVsDAAA2QC0+JF122WX5/ve/nxEjRmSXXXbJtddemw4dOuSGG26odGsAAMAGqEWHpJUrV+bpp5/O0KFDG8ZatWqVoUOH5tFHH61gZwAAwIaqRd8n6d13382qVavSrVu3RuPdunXLn/70p3V+T21tbWpraxuWFy9enCR5//33U1dXV7pm11Obj5eXvMZ7772n9kZS+9Pqbqy1N+T3Wm21N4baLXG/UsnaG/J7rbbalbJ06dIkSX19/Wc+r6r+rz2jgubNm5dtttkmjzzySAYOHNgwftZZZ2XmzJl5/PHH1/qeMWPG5Pzzzy9nmwAAwJfIm2++mZ49e37q4y16JmmLLbZI69ats3DhwkbjCxcuTPfu3df5Peecc05Gjx7dsLx69eq8//772XzzzVNVVVXSfpvbkiVL0qtXr7z55pvp3Lmz2mqrrbbaX7K6aqutttobUt1K124O9fX1Wbp0aXr06PGZz2vRIalt27YZMGBApk2blsMPPzzJJ6Fn2rRpOfnkk9f5PTU1NampqWk01rVr1xJ3WlqdO3eu2IdQbbXVVntDqr0xbrPaaqu94dbeGLe5OXTp0uWvPqdFh6QkGT16dE444YTstdde2XvvvfOLX/wiy5cvz4gRIyrdGgAAsAFq8SHpqKOOyjvvvJN///d/z4IFC/K1r30td99991oXcwAAAGgOLT4kJcnJJ5/8qYfXbchqampy3nnnrXX4oNpqq6222l+OumqrrbbaG1LdStcupxZ9dTsAAIBya9E3kwUAACg3IQkAAKBASAIAACgQkgAAAAqEpBbs0UcfTevWrXPIIYeUreaJJ56Yqqqqhq/NN988Bx54YF544YWy1F+wYEFOOeWUbLfddqmpqUmvXr1y6KGHZtq0aSWrWdzm6urqdOvWLfvvv39uuOGGrF69umR111W/+HXggQdWrParr75a8rprbhBdNGPGjFRVVWXRokUlrf/X+tgQa6+r3m9+85u0a9cul156aUXql6NmVVVV/vmf/3mtx0aNGpWqqqqceOKJJa09bty4RuO33357qqqqSlLzL7355ps56aST0qNHj7Rt2zZ9+vTJqaeemvfee6+kdYv7lbZt26Zfv3654IIL8vHHH5e07l/Wrq6uzrbbbpuzzjorK1asKHntd955Jz/4wQ/Su3fv1NTUpHv37hk2bFgefvjhktZd1z68+DVmzJiS1R40aFBOO+20tcYnTZqUrl27lqzuoYce+qm/Ix988MFUVVU1+98t1157bTp16tToc7xs2bJUV1dn0KBBjZ675nfZ7Nmzm7WH+vr6DB06NMOGDVvrsauvvjpdu3bNW2+91aw111izTZ/2NXjw4JLUrSQhqQWbMGFCTjnllDzwwAOZN29e2eoeeOCBmT9/fubPn59p06alTZs2+c53vlPyunPmzMmAAQNy//335+c//3n+8Ic/5O67787gwYMzatSoktZes81z5szJXXfdlcGDB+fUU0/Nd77znbL8Yi++5mu+fv3rX5e87qfV3nbbbctSm8q5/vrrc+yxx+aaa67JGWecUel2SqZXr16ZMmVKPvroo4axFStWZPLkyendu3dJa7dr1y4XX3xxPvjgg5LWWZfXXnste+21V1555ZX8+te/zquvvpprr70206ZNy8CBA/P++++XtP6a/corr7ySM844I2PGjMnPf/7zktb8y9qvvfZaLr/88lx33XU577zzSl53+PDhefbZZ3PjjTfm5Zdfzm9/+9sMGjSo5KG0uO/+xS9+kc6dOzcaO/PMM0tavxJGjhyZe++9d52BYOLEidlrr72y2267NWvNwYMHZ9myZXnqqacaxh588MF07949jz/+eKMgPn369PTu3Tvbb799s/ZQVVWViRMn5vHHH891113XMP7666/nrLPOyi9/+cv07NmzWWuu8bd/+7dr/a0wf/78XHfddamqqsoPf/jDktStpC/FfZI2RsuWLcvNN9+cp556KgsWLMikSZNy7rnnlqX2mv+AJUn37t1z9tln51vf+lbeeeedbLnlliWr+8Mf/jBVVVV54oknsskmmzSMf+UrX8lJJ51UsrpJ423eZpttsueee2afffbJfvvtl0mTJuUf/uEfyla/3CpZm8q45JJLct5552XKlCk54ogjKt1OSe25556ZPXt2br311hx77LFJkltvvTW9e/cu+T8Dhg4dmldffTVjx47NJZdcUtJaf2nUqFFp27Ztfv/736d9+/ZJkt69e2ePPfbI9ttvnx//+Me55pprSla/uF/5wQ9+kNtuuy2//e1vc84555Ss5rpq9+rVK0OHDs29996biy++uGQ1Fy1alAcffDAzZszIvvvumyTp06dP9t5775LVXKO4/+7SpUuqqqo2+H36d77znWy55ZaZNGlSfvKTnzSML1u2LFOnTi1JIN9pp52y9dZbZ8aMGdlnn32SfDK7cthhh+X+++/PY4891jCjNGPGjJLNrPTq1StXXHFFTj755BxwwAHp27dvRo4cmQMOOCB///d/X5KaSdK2bdu1PlcvvfRSzjzzzJx77rn57ne/W7LalWImqYX67//+7/Tv3z877bRTjjvuuNxwww2pxC2tli1bll/96lfp169fNt9885LVef/993P33Xdn1KhRjQLSGqWctv80Q4YMye67755bb7217LWhVH70ox/lwgsvzB133LHBB6Q1TjrppEycOLFh+YYbbsiIESNKXrd169a56KKL8stf/rJkh8Csy/vvv5977rknP/zhDxsC0hrdu3fPsccem5tvvrmsv1Pat2+flStXlq3eGi+++GIeeeSRtG3btqR1OnbsmI4dO+b2229PbW1tSWuRtGnTJscff3wmTZrU6HM8derUrFq1KkcffXRJ6g4ePDjTp09vWJ4+fXoGDRqUfffdt2H8o48+yuOPP17Sw89OOOGE7LfffjnppJNy1VVX5cUXX2w0s1QOixYtymGHHZZBgwblwgsvLGvtchGSWqgJEybkuOOOS/LJoQOLFy/OzJkzy1L7jjvuaNjhd+rUKb/97W9z8803p1Wr0n1cXn311dTX16d///4lq9EU/fv3z5w5c0pep/iar/m66KKLSl53XbXL9d+gdW3zQQcdVJbaG6u77rorl1xySf7nf/4n++23X6XbKZvjjjsuDz30UN5444288cYbefjhhxv2r6V2xBFH5Gtf+1pZDvda45VXXkl9fX123nnndT6+884754MPPsg777xT8l7q6+tz33335Z577smQIUNKXi/5f/uWdu3a5atf/Wrefvvt/Ou//mtJa7Zp0yaTJk3KjTfemK5du+Yb3/hGzj333LKdz7sxOumkkzJ79uxGfxtNnDgxw4cPT5cuXUpSc/DgwXn44Yfz8ccfZ+nSpXn22Wez77775tvf/nZmzJiR5JPzyWtra0t+js5//Md/5MUXX8xpp52W//iP/yjpkT5/afXq1TnmmGPSpk2b/Nd//VfZzrMsN4fbtUCzZs3KE088kdtuuy3JJzvfo446KhMmTFjr5MBSGDx4cMNhGB988EGuvvrqHHTQQXniiSfSp0+fktSsxCzZ+qivry/LD3/xNV9js802K3ndddVe10xeOeomyeOPP162P143RrvttlvefffdnHfeedl7773TsWPHSrdUFltuuWUOOeSQhv86H3LIIdliiy3KVv/iiy/OkCFDyn5uSCX3q2uCSl1dXcMfVKW8gEDRmn3L8uXLc/nll6dNmzYZPnx4yesOHz48hxxySB588ME89thjDf+UuP7660t2gZCNWf/+/fO3f/u3ueGGGzJo0KC8+uqrefDBB3PBBReUrOagQYOyfPnyPPnkk/nggw+y4447Zsstt8y+++6bESNGZMWKFZkxY0a22267kp/zuNVWW+Wf/umfcvvtt5f9ojjnnntuHn300TzxxBPp1KlTWWuXk5mkFmjChAn5+OOP06NHj7Rp0yZt2rTJNddck1tuuSWLFy8uef1NNtkk/fr1S79+/fI3f/M3uf7667N8+fL853/+Z8lq7rDDDqmqqsqf/vSnktVoipdeeqksFzEovuZrvsoVkv6y9tZbb12Ruv369cs222xTltobq2222SYzZszIn//85xx44IFZunRppVsqm5NOOqnhP/2lPsfxL33729/OsGHDynI+TpL069cvVVVVeemll9b5+EsvvZRNN920pP95Hjx4cJ577rm88sor+eijj3LjjTeW7R8wa/Ytu+++e2644YY8/vjjmTBhQllqt2vXLvvvv3/+7d/+LY888khOPPHEss4illvnzp3X+XfJokWLSjabUzRy5MjccsstWbp0aSZOnJjtt9++4ZywUujXr1969uyZ6dOnZ/r06Q21evTokV69euWRRx7J9OnTyzZruuZvxHKaMmVK/vf//t+ZMmVKdthhh7LWLjchqYX5+OOPc9NNN+XSSy/Nc8891/D1/PPPp0ePHmW74llRVVVVWrVq1ejqUM1ts802y7BhwzJ+/PgsX758rcfLdUnoovvvvz9/+MMfyvIfSCiXPn36ZObMmVmwYMFGFZQOPPDArFy5MnV1deu8fG6pjRs3Lr/73e/y6KOPlrzW5ptvnv333z9XX331WvvtBQsW5L/+679y1FFHlXSWfE1Q6d27d9n/iCtq1apVzj333PzkJz8p6e+wT7PLLrus83fahmKnnXbKM888s9b4M888kx133LHk9b/3ve+lVatWmTx5cm666aacdNJJJT/6Y/DgwZkxY0ZmzJjR6Oieb3/727nrrrvyxBNPbJCXw06S5557LiNHjsy4ceMqsh8tNyGphbnjjjvywQcfZOTIkdl1110bfQ0fPrws/w2rra3NggULsmDBgrz00ks55ZRTsmzZshx66KElrTt+/PisWrUqe++9d2655Za88soreemll3LllVdm4MCBJa29Zpv//Oc/55lnnslFF12Uww47LN/5zndy/PHHl7R2sX7x69133y15XTZOvXr1yowZM/L2229n2LBhWbJkSVnqLl68uNE/f5577rm8+eabZandunXrvPTSS/n//r//L61bty5LzaKvfvWrOfbYY3PllVeWpd5VV12V2traDBs2LA888EDefPPN3H333dl///2zzTbb5Gc/+1lZ+mgJvvvd76Z169YZP358yWq89957GTJkSH71q1/lhRdeyOuvv56pU6fmkksuyWGHHVayupX2gx/8IC+//HL+5V/+JS+88EJmzZqVyy67LL/+9a/LcmuBjh075qijjso555yT+fPnl+WwxsGDB+ehhx7Kc88912jWat999811112XlStXbpAh6d13383hhx+eQYMG5bjjjlvrb5ZynONYbs5JamEmTJiQoUOHrnOaevjw4bnkkkvywgsvNPv1/4vuvvvuhkOuOnXqlP79+2fq1KklPx9qu+22yzPPPJOf/exnOeOMMzJ//vxsueWWGTBgQEkvVZv8v21u06ZNNt100+y+++658sorc8IJJ5T0ghV/Wb9op512anGHH9J8Vq9eXdH/sPfs2bPhMrXDhg3LPffck86dO5e05owZM7LHHns0Ghs5cmSuv/76ktZdo9Tb99dccMEFufnmm8tSa4cddshTTz2V8847L9/73vfy/vvvp3v37jn88MNz3nnnle1w3pagTZs2Ofnkk3PJJZfkBz/4QUkO++vYsWO+/vWv5/LLL8/s2bNTV1eXXr165fvf/37Zbt9RCdttt10eeOCB/PjHP87QoUOzcuXKhr8ZynFD9OSTfciECRNy8MEHp0ePHiWvN3jw4Hz00Ufp379/unXr1jC+7777ZunSpQ2XCt/Q3HnnnQ0Xv1nX9vXp06csF7oqp6r6lnrGPMAG7MADD0y/fv1y1VVXVboVAOAvONwOoIw++OCD3HHHHZkxY0aGDh1a6XYAgHVwuB1AGZ100kl58sknc8YZZ2zQ5yoAwJeZw+0AAAAKHG4HAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAHQopx44ok5/PDDm/S9gwYNymmnndZobMaMGamqqsqiRYu+cG8AbByEJABYD/X19fn4448r3QYAZSAkAfCl8eKLL+aggw5Kx44d061bt/z93/993n333SSfzEDNnDkzV1xxRaqqqlJVVZU5c+Zk8ODBSZJNN900VVVVOfHEE5Mkq1evztixY7Ptttumffv22X333fOb3/ymodaaGai77rorAwYMSE1NTR566KE8//zzGTx4cDp16pTOnTtnwIABeeqpp8r+WgBQOkISAF8KixYtypAhQ7LHHnvkqaeeyt13352FCxfme9/7XpLkiiuuyMCBA/P9738/8+fPz/z589OrV6/ccsstSZJZs2Zl/vz5ueKKK5IkY8eOzU033ZRrr702f/zjH3P66afnuOOOy8yZMxvVPfvsszNu3Li89NJL2W233XLsscemZ8+eefLJJ/P000/n7LPPTnV1dXlfDABKqk2lGwCA9XHVVVdljz32yEUXXdQwdsMNN6RXr155+eWXs+OOO6Zt27bp0KFDunfv3vCczTbbLEmy1VZbpWvXrkmS2traXHTRRbnvvvsycODAJMl2222Xhx56KNddd1323Xffhu+/4IILsv/++zcsz507N//6r/+a/v37J0l22GGHkm0zAJUhJAHwpfD8889n+vTp6dix41qPzZ49OzvuuON6r+vVV1/Nhx9+2Cj8JMnKlSuzxx57NBrba6+9Gi2PHj06//AP/5D/83/+T4YOHZrvfve72X777T/HlgDQ0glJAHwpLFu2LIceemguvvjitR7beuutP/e6kuTOO+/MNtts0+ixmpqaRsubbLJJo+UxY8bkmGOOyZ133pm77ror5513XqZMmZIjjjjic/UAQMslJAHwpbDnnnvmlltuSd++fdOmzbp/fbVt2zarVq1aayxJo/FddtklNTU1mTt3bqND69bXjjvumB133DGnn356jj766EycOFFIAtiAuHADAC3O4sWL89xzzzX6+sd//Me8//77Ofroo/Pkk09m9uzZueeeezJixIiGANS3b988/vjjmTNnTt59992sXr06ffr0SVVVVe6444688847WbZsWTp16pQzzzwzp59+em688cbMnj07zzzzTH75y1/mxhtv/NS+Pvroo5x88smZMWNG3njjjTz88MN58skns/POO5frpQGgDMwkAdDizJgxY61zg0aOHJmHH344P/rRj3LAAQektrY2ffr0yYEHHphWrT75n9+ZZ56ZE044Ibvssks++uijvP766+nbt2/OP//8nH322RkxYkSOP/74TJo0KRdeeGG23HLLjB07Nq+99lq6du2aPffcM+eee+6n9tW6deu89957Of7447Nw4cJsscUWOfLII3P++eeX9PUAoLyq6uvr6yvdBAAAQEvhcDsAAIACIQkAAKBASAIAACgQkgAAAAqEJAAAgAIhCQAAoEBIAgAAKBCSAAAACoQkAACAAiEJAACgQEgCAAAoEJIAAAAK/n95UQtdtD+MOAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "we can clearly see the same patterns just shifted by 7\n", + "\n" + ] + } + ], + "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": 107, + "id": "d91d2bcb-d35b-41d9-b4ce-76881dbcc398", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using permutation cipher with key: [288, 323, 113, 437, 306, 301, 389, 139, 372, 90]...\n", + "we will get ciphertext: ['I', 'I', 'E', 'E', 'O', 'I', 'O', 'E', 'S', 'B']...\n", + "with frequency diagram:\n" + ] + }, + { + "data": { + "image/png": "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tq+pNJ9p5Webbzjsa2h+6/o93+Luk7Ry5crU1tZm7733Ttu2bTNt2rSG52bPnp3XXnst/fr1q2CHAADA+qyiM0mjRo3KYYcdll69emXJkiWZPHlyZsyYkTvuuCObbLJJhg8fnpEjR6Zr167p3LlzTj311PTr18+V7QAAgJKpaEh68803c+KJJ2bevHnZZJNNsvvuu+eOO+7IIYcckiS5/PLL06pVqwwZMiS1tbUZNGhQrr766kq2DAAArOcqGpLGjx//ic+3b98+Y8eOzdixY8vUEQAAsKFrceckAQAAVJKQBAAAUFDxS4ADrA96n3NbyWvMGTO45DWA1ZX659vPNrQ8ZpIAAAAKhCQAAIACIQkAAKBASAIAACgQkgAAAAqEJAAAgAIhCQAAoKBJIenll19u7j4AAABahCaFpD59+qSmpia//vWvs3z58ubuCQAAoGKaFJKeeOKJ7L777hk5cmS6deuW7373u3n00UebuzcAAICya1JI+uIXv5grrrgic+fOzYQJEzJv3rx85StfyW677ZbLLrssb731VnP3CQAAUBaf6cINbdq0yTHHHJMpU6bk4osvzosvvpizzjorPXv2zIknnph58+Y1V58AAABl8ZlC0qxZs/L9738/3bt3z2WXXZazzjorL730Uu66667MnTs3Rx55ZHP1CQAAUBZtmvJFl112WSZOnJjZs2fn8MMPz/XXX5/DDz88rVp9nLm22267TJo0Kb17927OXgEAAEquSSHpmmuuycknn5yhQ4eme/fua3zNlltumfHjx3+m5gAAAMqtSSHphRde+IevadeuXU466aSmvD0AAEDFNOmcpIkTJ2bKlCmrjU+ZMiXXXXfdZ24KAACgUpoUki666KJsvvnmq41vueWWufDCCz9zUwAAAJXSpJD02muvZbvttlttfNttt81rr732mZsCAAColCaFpC233DLPPPPMauNPP/10Nttss8/cFAAAQKU0KSR985vfzL//+79n+vTpWbFiRVasWJF77rknp512Wv7lX/6luXsEAAAomyZd3e6CCy7InDlzcvDBB6dNm4/fYuXKlTnxxBOdkwQAAHyuNSkktWvXLjfeeGMuuOCCPP3009loo43yhS98Idtuu21z9wcAAFBWTQpJq/Tt2zd9+/Ztrl4AAAAqrkkhacWKFZk0aVKmTZuWN998MytXrmz0/D333NMszQEAAJRbk0LSaaedlkmTJmXw4MHZbbfdUlVV1dx9AQAAVESTQtINN9yQ3/72tzn88MObux8AAICKatIlwNu1a5c+ffo0dy8AAAAV16SQdOaZZ+aKK65IfX19c/cDAABQUU063O7+++/P9OnTc/vtt+ef/umf0rZt20bP33zzzc3SHAAAQLk1KSR16dIlRx99dHP3AgAAUHFNCkkTJ05s7j4AAABahCadk5QkH330Ue6+++5ce+21WbJkSZJk7ty5Wbp0abM1BwAAUG5Nmkl69dVXc+ihh+a1115LbW1tDjnkkHTq1CkXX3xxamtrM27cuObuEwAAoCyaNJN02mmnZZ999sl7772XjTbaqGH86KOPzrRp05qtOQAAgHJr0kzSfffdlwcffDDt2rVrNN67d+/87W9/a5bGAAAAKqFJM0krV67MihUrVht/44030qlTp8/cFAAAQKU0KSQNHDgwv/jFLxqWq6qqsnTp0px77rk5/PDDm6s3AACAsmvS4XaXXnppBg0alF133TXLly/PcccdlxdeeCGbb755fvOb3zR3jwAAAGXTpJDUo0ePPP3007nhhhvyzDPPZOnSpRk+fHiOP/74RhdyAAAA+LxpUkhKkjZt2uSEE05ozl4AAAAqrkkh6frrr//E50888cQmNQMAAFBpTQpJp512WqPlurq6vP/++2nXrl06dOggJAEAAJ9bTbq63XvvvdfosXTp0syePTtf+cpXXLgBAAD4XGtSSFqTHXfcMWPGjFltlgkAAODzpNlCUvLxxRzmzp3bnG8JAABQVk06J+mPf/xjo+X6+vrMmzcvV111Vb785S83S2MAAACV0KSQdNRRRzVarqqqyhZbbJH+/fvn0ksvbY6+AAAAKqJJIWnlypXN3QcAAECL0KznJAEAAHzeNWkmaeTIkev82ssuu6wpJQAAACqiSSHpySefzJNPPpm6urrstNNOSZLnn38+rVu3zl577dXwuqqqqubpEgAAoEyaFJKOOOKIdOrUKdddd1023XTTJB/fYHbYsGH56le/mjPPPLNZmwQAACiXJp2TdOmll+aiiy5qCEhJsummm+ZnP/uZq9sBAACfa00KSYsXL85bb7212vhbb72VJUuWfOamAAAAKqVJIenoo4/OsGHDcvPNN+eNN97IG2+8kZtuuinDhw/PMccc09w9AgAAlE2TzkkaN25czjrrrBx33HGpq6v7+I3atMnw4cPz85//vFkbBAAAKKcmhaQOHTrk6quvzs9//vO89NJLSZIddtghG2+8cbM2BwAAUG6f6Way8+bNy7x587Ljjjtm4403Tn19fXP1BQAAUBFNCknvvPNODj744PTt2zeHH3545s2blyQZPny4y38DAACfa00KSWeccUbatm2b1157LR06dGgYP/bYYzN16tRmaw4AAKDcmnRO0p133pk77rgjPXr0aDS+44475tVXX22WxgAAACqhSTNJy5YtazSDtMq7776b6urqz9wUAABApTQpJH31q1/N9ddf37BcVVWVlStX5pJLLklNTU2zNQcAAFBuTTrc7pJLLsnBBx+cWbNm5cMPP8zZZ5+dv/zlL3n33XfzwAMPNHePAAAAZdOkkLTbbrvl+eefz1VXXZVOnTpl6dKlOeaYYzJixIh07969uXsEgBal9zm3lbzGnDGDS14DgDX71CGprq4uhx56aMaNG5cf/ehHpegJAACgYj71OUlt27bNM888U4peAAAAKq5JF2444YQTMn78+ObuBQAAoOKadE7SRx99lAkTJuTuu+/O3nvvnY033rjR85dddlmzNAcAAFBunyokvfzyy+ndu3eeffbZ7LXXXkmS559/vtFrqqqqmq87AACAMvtUIWnHHXfMvHnzMn369CTJsccemyuvvDJbbbVVSZoDAAAot091TlJ9fX2j5dtvvz3Lli1r1oYAAAAqqUkXbljl70MTAADA592nOtyuqqpqtXOOnIMEAJRaqW/g6+a9QNGnCkn19fUZOnRoqqurkyTLly/Pv/3bv612dbubb765+ToEAAAoo08Vkk466aRGyyeccEKzNgMAAFBpnyokTZw4sVR9AAAAtAif6cINAAAA65uKhqSLLroo//zP/5xOnTplyy23zFFHHZXZs2c3es3y5cszYsSIbLbZZunYsWOGDBmSBQsWVKhjAABgfVfRkDRz5syMGDEiDz/8cO66667U1dVl4MCBje69dMYZZ+RPf/pTpkyZkpkzZ2bu3Lk55phjKtg1AACwPvtU5yQ1t6lTpzZanjRpUrbccss8/vjjOeCAA7Jo0aKMHz8+kydPTv/+/ZN8fF7ULrvskocffjj7779/JdoGAADWYxUNSX9v0aJFSZKuXbsmSR5//PHU1dVlwIABDa/Zeeed06tXrzz0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bbXVVvtzVldttdVWe32qW+nazaG+vj5LlizJ1ltv/Ymva9EhafPNN0/r1q2zYMGCRuMLFixIt27d1vg11dXVqa6ubjTWpUuXUrVYFp07d67YN6Haaqut9vpUe0NcZ7XVVnv9rb0hrnNz2GSTTf7ha1r0hRvatWuXvffeO9OmTWsYW7lyZaZNm9bo8DsAAIDm0qJnkpJk5MiROemkk7LPPvtk3333zS9+8YssW7Ysw4YNq3RrAADAeqjFh6Rjjz02b731Vn76059m/vz5+eIXv5ipU6dmq622qnRrJVddXZ1zzz13tcMH1VZbbbXV/nzUVVtttdVen+pWunY5teir2wEAAJRbiz4nCQAAoNyEJAAAgAIhCQAAoEBIAgAAKBCSWrCHHnoorVu3zuDBg8tWc+jQoamqqmp4bLbZZjn00EPzzDPPlKX+/Pnzc+qpp2b77bdPdXV1evbsmSOOOKLRvbKaW3Gd27Ztm6222iqHHHJIJkyYkJUrV5as7prqFx+HHnpoxWq/+OKLJa89f/78nHbaaenTp0/at2+frbbaKl/+8pdzzTXX5P333y9Z3aFDh+aoo45abXzGjBmpqqrKwoULS1Z7XfooZ73f/e53ad++fS699NKK1C9Hzaqqqvzbv/3bas+NGDEiVVVVGTp0aElrjxkzptH4LbfckqqqqpLU/Huvv/56Tj755Gy99dZp165dtt1225x22ml55513Slq3+LnSrl279OnTJ+eff34++uijktb9+9pt27bNdtttl7PPPjvLly8vee233nor3/ve99KrV69UV1enW7duGTRoUB544IGS1l3TZ3jxMXr06JLVPuigg3L66aevNj5p0qR06dKlZHWPOOKItf6OvO+++1JVVdXsf7eMGzcunTp1avR9vHTp0rRt2zYHHXRQo9eu+n3y0ksvNWsPK1asyJe+9KUcc8wxjcYXLVqUnj175kc/+lGz1iuqr6/PgAEDMmjQoNWeu/rqq9OlS5e88cYbJatfCUJSCzZ+/PiceuqpuffeezN37tyy1T300EMzb968zJs3L9OmTUubNm3yta99reR158yZk7333jv33HNPfv7zn+fPf/5zpk6dmpqamowYMaKktVet85w5c3L77benpqYmp512Wr72ta+V5Rd7cZuvevzmN78ped211d5uu+1KWvPll1/OnnvumTvvvDMXXnhhnnzyyTz00EM5++yzc+utt+buu+8uaX2SX/3qVzn++ONzzTXX5Mwzz6x0OyXTs2fP3HDDDfnggw8axpYvX57JkyenV69eJa3dvn37XHzxxXnvvfdKWmdNXn755eyzzz554YUX8pvf/CYvvvhixo0b13Az9nfffbek9Vd9rrzwwgs588wzM3r06Pz85z8vac2/r/3yyy/n8ssvz7XXXptzzz235HWHDBmSJ598Mtddd12ef/75/PGPf8xBBx1U8lBa/Oz+xS9+kc6dOzcaO+uss0pavxKGDx+eu+66a41/lE+cODH77LNPdt9992atWVNTk6VLl2bWrFkNY/fdd1+6deuWRx55pFEQnz59enr16pUddtihWXto3bp1Jk2alKlTp+Z//ud/GsZPPfXUdO3ataTf51VVVZk4cWIeeeSRXHvttQ3jr7zySs4+++z88pe/TI8ePUpWvxJa/H2SNlRLly7NjTfemFmzZmX+/PmZNGlSfvjDH5al9qr/gCVJt27dcs455+SrX/1q3nrrrWyxxRYlq/v9738/VVVVefTRR7Pxxhs3jP/TP/1TTj755JLVTRqv8zbbbJO99tor+++/fw4++OBMmjQp//qv/1q2+uVWidrf//7306ZNm8yaNavRvt5+++1z5JFHxp0JSuuSSy7JueeemxtuuCFHH310pdspqb322isvvfRSbr755hx//PFJkptvvjm9evUq+T8DBgwYkBdffDEXXXRRLrnkkpLW+nsjRoxIu3btcuedd2ajjTZKkvTq1St77rlndthhh/zoRz/KNddcU7L6xc+V733ve/n973+fP/7xjxk1alTJaq6pds+ePTNgwIDcddddufjii0tWc+HChbnvvvsyY8aMHHjggUmSbbfdNvvuu2/Jaq5S/PzeZJNNUlVVVbHfJ+Xyta99LVtssUUmTZqUH//4xw3jS5cuzZQpU0oSyHfaaad07949M2bMyP7775/k4xmjI488Mvfcc08efvjhhhmlGTNmpKamptl7SJK+fftmzJgxOfXUU9O/f/88+uijueGGG/LYY4+lXbt2Jam5Ss+ePXPFFVfklFNOycCBA9O7d+8MHz48AwcOzLe+9a2S1q4EM0kt1G9/+9vsvPPO2WmnnXLCCSdkwoQJFfnDcenSpfn1r3+dPn36ZLPNNitZnXfffTdTp07NiBEjGv3RvEopp+3Xpn///tljjz1y8803l732+uydd97JnXfeudZ9naRshyNtiH7wgx/kggsuyK233rreB6RVTj755EycOLFhecKECRk2bFjJ67Zu3ToXXnhhfvnLX5b1MJR33303d9xxR77//e83BKRVunXrluOPPz433nhjWX+nbLTRRvnwww/LVm+VZ599Ng8++GDJ/3js2LFjOnbsmFtuuSW1tbUlrUXSpk2bnHjiiZk0aVKj7+MpU6ZkxYoV+eY3v1mSujU1NZk+fXrD8vTp03PQQQflwAMPbBj/4IMP8sgjj5QsJCUfzxztscce+da3vpXvfOc7+elPf5o99tijZPWKTjrppBx88ME5+eSTc9VVV+XZZ59tNLO0PhGSWqjx48fnhBNOSPLxoQOLFi3KzJkzy1L71ltvbfjA79SpU/74xz/mxhtvTKtWpft2efHFF1NfX5+dd965ZDWaYuedd86cOXNKXqe4zVc9LrzwwpLXXVPtr3/96yWtt2pf77TTTo3GN99884YefvCDH5S0hzVt78MOO6ykNVuC22+/PZdcckn+8Ic/5OCDD650O2Vzwgkn5P7778+rr76aV199NQ888EDD52upHX300fniF79YlsO9VnnhhRdSX1+fXXbZZY3P77LLLnnvvffy1ltvlbyX+vr63H333bnjjjvSv3//ktdL/t/Pd/v27fOFL3whb775Zv7jP/6jpDXbtGmTSZMm5brrrkuXLl3y5S9/OT/84Q/Ldj7vhujkk0/OSy+91Ohvo4kTJ2bIkCHZZJNNSlKzpqYmDzzwQD766KMsWbIkTz75ZA488MAccMABmTFjRpKPzyevra0taUiqqqrKNddck2nTpmWrrbbKOeecU7Jaa/Jf//VfefbZZ3P66afnv/7rv0p6lFElOdyuBZo9e3YeffTR/P73v0/y8Yfvsccem/Hjx692cmAp1NTUNByG8d577+Xqq6/OYYcdlkcffTTbbrttSWq21MOr6uvryzKrUdzmq3Tt2rXkdddUe22zO6X26KOPZuXKlTn++ONL/p/YNW3vRx55pGx/OFfK7rvvnrfffjvnnntu9t1333Ts2LHSLZXFFltskcGDBzf813nw4MHZfPPNy1b/4osvTv/+/ct+bkglP1dXBZW6urqsXLkyxx13XEkvIFC06ud72bJlufzyy9OmTZsMGTKk5HWHDBmSwYMH57777svDDz/c8E+JX/3qVyW7QMiGbOedd86XvvSlTJgwIQcddFBefPHF3HfffTn//PNLVvOggw7KsmXL8thjj+W9995L3759s8UWW+TAAw/MsGHDsnz58syYMSPbb799yc95nDBhQjp06JBXXnklb7zxRnr37l3SekVbbrllvvvd7+aWW24p+wV5yslMUgs0fvz4fPTRR9l6663Tpk2btGnTJtdcc01uuummLFq0qOT1N9544/Tp0yd9+vTJP//zP+dXv/pVli1blv/+7/8uWc0dd9wxVVVV+etf/1qyGk3x3HPPlfy8haTxNl/1KFdI+vva3bt3L2m9Pn36pKqqKrNnz240vv3226dPnz6rHR5UCmva3ttss03J61baNttskxkzZuRvf/tbDj300CxZsqTSLZXNySef3PCf/lKf4/j3DjjggAwaNKgs5+Mk/+9n7Lnnnlvj888991w23XTTkv73t6amJk899VReeOGFfPDBB7nuuuvK9g+YVT/fe+yxRyZMmJBHHnkk48ePL0vt9u3b55BDDslPfvKTPPjggxk6dGhZZxHLrXPnzmv8u2ThwoUlm80pGj58eG666aYsWbIkEydOzA477NBwTlgp9OnTJz169Mj06dMzffr0hlpbb711evbsmQcffDDTp08v+azpgw8+mMsvvzy33npr9t133wwfPrzs/xRZ9ffp+kxIamE++uijXH/99bn00kvz1FNPNTyefvrpbL311mW74llRVVVVWrVq1ejqUM2ta9euGTRoUMaOHZtly5at9ny5LstcdM899+TPf/5zWf4DuSHZbLPNcsghh+Sqq65a476mtLbddtvMnDkz8+fP36CC0qGHHpoPP/wwdXV1a7yEbamNGTMmf/rTn/LQQw+VvNaqn7Grr756tc/t+fPn53/+539y7LHHlnSWfFVQ6dWrV0X/kGrVqlV++MMf5sc//nFJf4etza677rpef87ttNNOeeKJJ1Ybf+KJJ9K3b9+S1//GN76RVq1aZfLkybn++utz8sknl/zoj5qamsyYMSMzZsxodHTPAQcckNtvvz2PPvpoSQ+1e//99zN06NB873vfS01NTcaPH59HH30048aNK1nNDZWQ1MLceuutee+99zJ8+PDstttujR5Dhgwpy3/DamtrM3/+/MyfPz/PPfdcTj311CxdujRHHHFESeuOHTs2K1asyL777pubbropL7zwQp577rlceeWV6devX0lrr1rnv/3tb3niiSdy4YUX5sgjj8zXvva1nHjiiSWtXaxffLz99tslr1spV199dT766KPss88+ufHGG/Pcc89l9uzZ+fWvf52//vWvad26daVbXK/17NkzM2bMyJtvvplBgwZl8eLFZam7aNGiRv/8eeqpp/L666+XpXbr1q3z3HPP5f/7//6/inx/feELX8jxxx+fK6+8siz1rrrqqtTW1mbQoEG599578/rrr2fq1Kk55JBDss022+Q///M/y9JHS/D1r389rVu3ztixY0tW45133kn//v3z61//Os8880xeeeWVTJkyJZdcckmOPPLIktWttO9973t5/vnn8+///u955plnMnv27Fx22WX5zW9+U5ZbC3Ts2DHHHntsRo0alXnz5pXlsMaamprcf//9eeqppxrNWh144IG59tpr8+GHH5Y0JI0aNSr19fUN92Dr3bt3/vf//t85++yzy3IO9YZESGphxo8fnwEDBqxxmnrIkCGZNWtWyU8EnTp1arp3757u3btnv/32y2OPPZYpU6aU/Hyo7bffPk888URqampy5plnZrfddsshhxySadOmlfRStcn/W+fevXvn0EMPzfTp03PllVfmD3/4Q1n+oCpu81WPr3zlKyWvWyk77LBDnnzyyQwYMCCjRo3KHnvskX322Se//OUvc9ZZZ+WCCy6odIvrvR49emTGjBl5++23yxaUZsyYkT333LPR47zzzit53VU6d+6czp07l63e3zv//PPLcoPq5ONDmGfNmpXtt98+3/jGN7LDDjvkO9/5TmpqavLQQw+V7XDelqBNmzY55ZRTcskll5RsVqdjx47Zb7/9cvnll+eAAw7Ibrvtlp/85Cf59re/nauuuqokNVuC7bffPvfee2/++te/ZsCAAdlvv/3y29/+NlOmTCnLDdGTjw+5e++99zJo0KBsvfXWJa9XU1OTDz74IH369MlWW23VMH7ggQdmyZIlDZcKL4WZM2dm7NixmThxYjp06NAw/t3vfjdf+tKXKnLY3fqsqt7WBAAAaGAmCQAAoEBIAgAAKBCSAAAACoQkAACAAiEJAACgQEgCAAAoEJIAAAAKhCQAAIACIQmAFmXo0KE56qijmvS1Bx10UE4//fRGYzNmzEhVVVUWLlz4mXsDYMMgJAHAOqivr89HH31U6TYAKAMhCYDPjWeffTaHHXZYOnbsmK222irf+ta38vbbbyf5eAZq5syZueKKK1JVVZWqqqrMmTMnNTU1SZJNN900VVVVGTp0aJJk5cqVueiii7Lddttlo402yh577JHf/e53DbVWzUDdfvvt2XvvvVNdXZ37778/Tz/9dGpqatKpU6d07tw5e++9d2bNmlX2bQFA6QhJAHwuLFy4MP3798+ee+6ZWbNmZerUqVmwYEG+8Y1vJEmuuOKK9OvXL9/+9rczb968zJs3Lz179sxNN92UJJk9e3bmzZuXK664Ikly0UUX5frrr8+4cePyl7/8JWeccUZOOOGEzJw5s1Hdc845J2PGjMlzzz2X3XffPccff3x69OiRxx57LI8//njOOeectG3btrwbA4CSalPpBgBgXVx11VXZc889c+GFFzaMTZgwIT179szzzz+fvn37pl27dunQoUO6devW8JquXbsmSbbccst06dIlSVJbW5sLL7wwd999d/r165ck2X777XP//ffn2muvzYEHHtjw9eeff34OOeSQhuXXXnst//Ef/5Gdd945SbLjjjuWbJ0BqAwhCYDPhaeffjrTp09Px44dV3vupZdeSt++fdf5vV588cW8//77jcJPknz44YfZc889G43ts88+jZZHjhyZf/3Xf83/+T//JwMGDMjXv/717LDDDp9iTQBo6YQkAD4Xli5dmiOOOCIXX3zxas917979U79Xktx2223ZZpttGj1XXV3daHnjjTdutDx69Ogcd9xxue2223L77bfn3HPPzQ033JCjjz76U/UAQMslJAHwubDXXnvlpptuSu/evdOmzZp/fbVr1y4rVqxYbSxJo/Fdd9011dXVee211xodWreu+vbtm759++aMM87IN7/5zUycOFFIAliPuHADAC3OokWL8tRTTzV6fOc738m7776bb37zm3nsscfy0ksv5Y477siwYcMaAlDv3r3zyCOPZM6cOXn77bezcuXKbLvttqmqqsqtt96at956K0uXLk2nTp1y1lln5Ywzzsh1112Xl156KU888UR++ctf5rrrrltrXx988EFOOeWUzJgxI6+++moeeOCBPPbYY9lll13KtWkAKAMzSQC0ODNmzFjt3KDhw4fngQceyA9+8IMMHDgwtbW12XbbbXPooYemVauP/+d31lln5aSTTsquu+6aDz74IK+88kp69+6d8847L+ecc06GDRuWE088MZMmTcoFF1yQLbbYIhdddFFefvnldOnSJXvttVd++MMfrrWv1q1b55133smJJ56YBQsWZPPNN88xxxyT8847r6TbA4Dyqqqvr6+vdBMAAAAthcPtAAAACoQkAACAAiEJAACgQEgCAAAoEJIAAAAKhCQAAIACIQkAAKBASAIAACgQkgAAAAqEJAAAgAIhCQAAoEBIAgAAKPj/Adf9dkneZ8JLAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "we can see there is no change compared to the plaintext diagram as permutation does not create any confusion\n", + "\n" + ] + } + ], + "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": 108, + "id": "19b0e732-32de-4577-8e70-97e3ab059c26", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using vigenere cipher with key: LKVCAEUIKN\n", + "we will get ciphertext: ['L', 'M', 'X', 'Q', 'R', 'H', 'C', 'V', 'Q', 'G']...\n", + "with frequency diagram:\n" + ] + }, + { + "data": { + "image/png": "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ksyZMyeLFi0qW3MAAADV1qIjSS+++GKGDRuWl156KfX19dl///3TpUuXnHfeeamvr8/ll19e7j4BAACqokVHkk4++eTsvvvueeutt7Leeus1LT/ssMNy1113la05AACAamvRkaR77703DzzwQDp27Nhseb9+/fL3v/+9LI0BAADUQouOJC1dujRLlixZbvkrr7ySLl26fOymAAAAaqVFIemAAw7Iz372s6bnpVIpixYtyllnnZXPf/7z5eoNAACg6lp0ut1Pf/rTDB06NDvssEPee++9HHXUUXnuueeyySab5Le//W25ewQAAKiaFoWkXr165cknn8y1116bp556KosWLcrIkSNz9NFHN7uRAwAAwJqmRSEpSdq3b59jjjmmnL0AAADUXItC0jXXXPORrx977LEtagYAAKDWWhSSTj755GbPGxoa8s4776Rjx47p1KmTkAQAAKyxWnR3u7feeqvZY9GiRZkxY0b22msvN24AAADWaC2+JumfbbPNNhk3blyOOeaY/O1vfyvXsABAK9Xv9FsqMu7scQdVZFwoFz/7bV+LjiStTPv27TNnzpxyDgkAAFBVLTqS9Mc//rHZ88bGxsydOzcXX3xxPve5z5WlMQAAgFpoUUg69NBDmz0vlUrZdNNNs+++++anP/1pOfoCAACoiRaFpKVLl5a7DwAAgFahrNckAQAArOladCRp9OjRq7zuBRdc0JISAAAANdGikPTEE0/kiSeeSENDQ7bddtskybPPPpt27dpl1113bVqvVCqVp0sAAIAqaVFIOvjgg9OlS5dcffXV2WijjZJ8+AWzxx9/fP7t3/4t3/nOd8raJAAAQLW06Jqkn/70pzn33HObAlKSbLTRRvnxj3/s7nYAAMAarUUhaeHChXnttdeWW/7aa6/l7bffXuVx7rnnnhx88MHp2bNnSqVSbrrppmavjxgxIqVSqdlj2LBhLWkZAABglbQoJB122GE5/vjjc8MNN+SVV17JK6+8kuuvvz4jR47M4YcfvsrjLF68ODvvvHMuueSSla4zbNiwzJ07t+nx29/+tiUtAwAArJIWXZN0+eWX59RTT81RRx2VhoaGDwdq3z4jR47MT37yk1Ue58ADD8yBBx74kevU1dWle/fuLWkTAABgtbUoJHXq1CmXXnppfvKTn2TWrFlJkq233jrrr79+WZtLkilTpmSzzTbLRhttlH333Tc//vGPs/HGG690/fr6+tTX1zc9X7hwYZKkoaGhKdBRGXXtGisybq3ft7Y6r8TcVldbnVey8rlVcztWc25ttVa1mdvqaw1zqyb74tXTGvbFbd2qzrvU2NjY4q0+c+bMzJo1K3vvvXfWW2+9NDY2tvi236VSKTfeeGMOPfTQpmXXXnttOnXqlC233DKzZs3KmWeemc6dO+fBBx9Mu3btVjjOmDFjMnbs2OWWT5w4MZ06dWpRbwAAwJrvnXfeyVFHHZUFCxaka9euK12vRSHpjTfeyJe//OVMnjw5pVIpzz33XLbaaquccMIJ2WijjVp0h7sVhaR/9vzzz2frrbfOnXfemf3222+F66zoSFLv3r3z+uuvf+SGqJYdx9xekXGfHjO0IuOujrY6t7Y6r8TcVldbnVey8rlVcztWc25ttVa1mdvqaw1zqyb74tXTGvbFbd3ChQuzySab/MuQ1KLT7U455ZR06NAhL730Urbffvum5UcccURGjx5dsduAb7XVVtlkk00yc+bMlYakurq61NXVLbe8Q4cO6dChQ0X6Wh31SyrzBbvmVjltdV6Jua2utjqvZOVzq+Z2rObc2mqtajO31dca5lZN9sWrpzXsi9u6VZ13i0LSn//859x+++3p1atXs+XbbLNNXnzxxZYMuUpeeeWVvPHGG+nRo0fFagAAAGu3FoWkxYsXr/D6njfffHOFR3FWZtGiRZk5c2bT8xdeeCHTp09Pt27d0q1bt4wdOzbDhw9P9+7dM2vWrJx22mnp379/hg5dOw8PAgAAldei70n6t3/7t1xzzTVNz0ulUpYuXZrzzz8/gwcPXuVxpk2bll122SW77LJLkmT06NHZZZdd8sMf/jDt2rXLU089lS9+8YsZMGBARo4cmd122y333nvvagUxAACA1dGiI0nnn39+9ttvv0ybNi3vv/9+TjvttPz1r3/Nm2++mfvvv3+Vxxk0aFA+6r4Rt99emYviAAAAVqZFR5J23HHHPPvss9lrr71yyCGHZPHixTn88MPzxBNPZOutty53jwAAAFWz2keSGhoaMmzYsFx++eX53ve+V4meAAAAama1jyR16NAhTz31VCV6AQAAqLkWnW53zDHH5Morryx3LwAAADXXohs3fPDBB7nqqqty5513Zrfddsv666/f7PULLrigLM0BAABU22qFpOeffz79+vXL008/nV133TVJ8uyzzzZbp1SqzDcQAwAAVMNqhaRtttkmc+fOzeTJk5MkRxxxRH7+859n8803r0hzAAAA1bZaIemfv9Po1ltvzeLFi8vaEKzt+p1+S0XGnT3uoIqMi/cMqqEt/z9ry3ODNVWLbtywzEd9ESwAAMCaaLVCUqlUWu6aI9cgAQAAbclqn243YsSI1NXVJUnee++9fOMb31ju7nY33HBD+ToEAACootUKSccdd1yz58ccc0xZmwEAAKi11QpJ48ePr1QfAAAArcLHunEDAABAWyMkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAEDBan2ZLGuWfqffUpFxZ487qCLjro62PDcAYPX5bEA5OZIEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQ0L7WDQAA5dPv9FsqMu7scQdVZFygdbEP+ZAjSQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABTUNCTdc889Ofjgg9OzZ8+USqXcdNNNzV5vbGzMD3/4w/To0SPrrbdehgwZkueee642zQIAAGuFmoakxYsXZ+edd84ll1yywtfPP//8/PznP8/ll1+ehx9+OOuvv36GDh2a9957r8qdAgAAa4v2tSx+4IEH5sADD1zha42NjfnZz36W73//+znkkEOSJNdcc00233zz3HTTTfmP//iParYKAACsJWoakj7KCy+8kHnz5mXIkCFNyzbYYIPsueeeefDBB1cakurr61NfX9/0fOHChUmShoaGNDQ0VLbpVVDXrrEi465obtWsVe16bbVWtetVe27VVIm5rY3vme24ZtWqdr22Wqva9eyLV8/a+J611e1YC6vaR6mxsbEyW2I1lUql3HjjjTn00EOTJA888EA+97nPZc6cOenRo0fTel/+8pdTKpVy3XXXrXCcMWPGZOzYscstnzhxYjp16lSR3gEAgNbvnXfeyVFHHZUFCxaka9euK12v1R5Jaqkzzjgjo0ePbnq+cOHC9O7dOwcccMBHbohq2XHM7RUZ9+kxQ2taq9r12mqtatdrq7UqVW9te88qVW9t245t4T1bWb22Wqva9ao9t2qyD/n4tSpVrzVsx1pYdpbZv9JqQ1L37t2TJK+++mqzI0mvvvpqPvWpT63039XV1aWurm655R06dEiHDh3K3ufqql9Sqsi4K5pbNWtVu15brVXtem21VqXqrW3vWaXqrW3bsS28Zyur11ZrVbtetedWTfYhH79Wpeq1hu1YC6vaR6v9nqQtt9wy3bt3z1133dW0bOHChXn44YczcODAGnYGAAC0ZTU9krRo0aLMnDmz6fkLL7yQ6dOnp1u3bunTp0++/e1v58c//nG22WabbLnllvnBD36Qnj17Nl23BAAAUG41DUnTpk3L4MGDm54vu5bouOOOy4QJE3Laaadl8eLF+drXvpb58+dnr732ym233ZZ11123Vi0DAABtXE1D0qBBg/JRN9crlUo5++yzc/bZZ1exKwAAYG3Waq9JAgAAqAUhCQAAoEBIAgAAKBCSAAAACoQkAACAAiEJAACgQEgCAAAoqOn3JAEAUD39Tr+lIuPOHndQRcaFWnEkCQAAoEBIAgAAKBCSAAAACoQkAACAAiEJAACgQEgCAAAoEJIAAAAKhCQAAIACIQkAAKBASAIAACgQkgAAAAqEJAAAgAIhCQAAoEBIAgAAKBCSAAAACoQkAACAAiEJAACgQEgCAAAoEJIAAAAKhCQAAIACIQkAAKBASAIAACgQkgAAAAqEJAAAgAIhCQAAoEBIAgAAKBCSAAAACoQkAACAAiEJAACgQEgCAAAoEJIAAAAKhCQAAIACIQkAAKBASAIAACgQkgAAAAqEJAAAgAIhCQAAoEBIAgAAKBCSAAAACoQkAACAAiEJAACgQEgCAAAoEJIAAAAKhCQAAIACIQkAAKBASAIAACgQkgAAAAqEJAAAgAIhCQAAoEBIAgAAKBCSAAAACoQkAACAglYdksaMGZNSqdTssd1229W6LQAAoA1rX+sG/pVPfOITufPOO5uet2/f6lsGAADWYK0+cbRv3z7du3evdRsAAMBaotWHpOeeey49e/bMuuuum4EDB+bcc89Nnz59Vrp+fX196uvrm54vXLgwSdLQ0JCGhoaK9/uv1LVrrMi4K5pbNWtVu15brVXtem21VqXqrW3vWaXqrW3bsS28Zyur11ZrVbteW61VqXpr23tWqXqtYTvWwqr2UWpsbKzMliiDW2+9NYsWLcq2226buXPnZuzYsfn73/+ep59+Ol26dFnhvxkzZkzGjh273PKJEyemU6dOlW4ZAABopd55550cddRRWbBgQbp27brS9Vp1SPpn8+fPT9++fXPBBRdk5MiRK1xnRUeSevfunddff/0jN0S17Djm9oqM+/SYoTWtVe16bbVWteu11VqVqre2vWeVqre2bce28J6trF5brVXtem21VqXqrW3vWaXqtYbtWAsLFy7MJpts8i9DUqs/3a5oww03zIABAzJz5syVrlNXV5e6urrllnfo0CEdOnSoZHurpH5JqSLjrmhu1axV7XpttVa167XVWpWqt7a9Z5Wqt7Ztx7bwnq2sXlutVe16bbVWpeqtbe9Zpeq1hu1YC6vaR6u+Bfg/W7RoUWbNmpUePXrUuhUAAKCNatUh6dRTT83UqVMze/bsPPDAAznssMPSrl27HHnkkbVuDQAAaKNa9el2r7zySo488si88cYb2XTTTbPXXnvloYceyqabblrr1gAAgDaqVYeka6+9ttYtAAAAa5lWfbodAABAtQlJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAFQhIAAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAULBGhKRLLrkk/fr1y7rrrps999wzjzzySK1bAgAA2qhWH5Kuu+66jB49OmeddVYef/zx7Lzzzhk6dGj+8Y9/1Lo1AACgDWr1IemCCy7IV7/61Rx//PHZYYcdcvnll6dTp0656qqrat0aAADQBrWvdQMf5f33389jjz2WM844o2nZOuuskyFDhuTBBx9c4b+pr69PfX190/MFCxYkSd588800NDRUtuFV0P6DxRUZ94033qhprWrXa6u1ql2vrdaqVL217T2rVL21bTu2hfdsZfXaaq1q12urtSpVb217zypVrzVsx1p4++23kySNjY0fuV6p8V+tUUNz5szJFltskQceeCADBw5sWn7aaadl6tSpefjhh5f7N2PGjMnYsWOr2SYAALAGefnll9OrV6+Vvt6qjyS1xBlnnJHRo0c3PV+6dGnefPPNbLzxximVSjXsbPUsXLgwvXv3zssvv5yuXbu2mVrVrtdWa1W7XlutVe16aq159dpqrWrXa6u1ql2vrdaqdj211sx65dLY2Ji33347PXv2/Mj1WnVI2mSTTdKuXbu8+uqrzZa/+uqr6d69+wr/TV1dXerq6pot23DDDSvVYsV17dq1aj941axV7XpttVa167XVWtWup9aaV6+t1qp2vbZaq9r12mqtatdTa82sVw4bbLDBv1ynVd+4oWPHjtltt91y1113NS1bunRp7rrrrman3wEAAJRLqz6SlCSjR4/Occcdl9133z177LFHfvazn2Xx4sU5/vjja90aAADQBrX6kHTEEUfktddeyw9/+MPMmzcvn/rUp3Lbbbdl8803r3VrFVVXV5ezzjpruVMH1/Ra1a7XVmtVu15brVXtemqtefXaaq1q12urtapdr63WqnY9tdbMetXWqu9uBwAAUG2t+pokAACAahOSAAAACoQkAACAAiEJAACgQEhqhR588MG0a9cuBx10UEXrjBgxIqVSqemx8cYbZ9iwYXnqqacqVnPevHk56aSTstVWW6Wuri69e/fOwQcf3Oy7sD6u4rw6dOiQzTffPPvvv3+uuuqqLF26tGx1VlSv+Bg2bFjZa31UvZkzZ5a91rx583LyySenf//+WXfddbP55pvnc5/7XC677LK88847ZaszYsSIHHroocstnzJlSkqlUubPn1+2Wqtae02ttaIav//977Puuuvmpz/9aVXqVaJGqVTKN77xjeVeGzVqVEqlUkaMGFH2euPGjWu2/KabbkqpVCpbnaKXX345J5xwQnr27JmOHTumb9++Ofnkk/PGG2+UvVZxH9KxY8f0798/Z599dj744IOy16rVvDp06JAtt9wyp512Wt57772y10qS1157Ld/85jfTp0+f1NXVpXv37hk6dGjuv//+stZZ0f6++BgzZkzZag0aNCjf/va3l1s+YcKEbLjhhmWrkyQHH3zwSn9P3nvvvSmVSh/788jll1+eLl26NPvZXrRoUTp06JBBgwY1W3fZ75tZs2Z9rJpJsmTJknz2s5/N4Ycf3mz5ggUL0rt373zve9/72DWWaWxszJAhQzJ06NDlXrv00kuz4YYb5pVXXilbvWXbaWWPwYMHl61WrQlJrdCVV16Zk046Kffcc0/mzJlT0VrDhg3L3LlzM3fu3Nx1111p3759vvCFL1Sk1uzZs7Pbbrvl7rvvzk9+8pP85S9/yW233ZbBgwdn1KhRZa21bF6zZ8/OrbfemsGDB+fkk0/OF77whYp8EChux2WP3/72t2Wv81H1ttxyy7LWeP7557PLLrvkz3/+c84555w88cQTefDBB3Paaafl5ptvzp133lnWelTGr371qxx99NG57LLL8p3vfKfW7bRY7969c+211+bdd99tWvbee+9l4sSJ6dOnT9nrrbvuujnvvPPy1ltvlX3sf/b8889n9913z3PPPZff/va3mTlzZi6//PKmL05/8803y15z2T7kueeey3e+852MGTMmP/nJT8pao5bzev7553PhhRfmiiuuyFlnnVX2OkkyfPjwPPHEE7n66qvz7LPP5o9//GMGDRpU9gBY3M//7Gc/S9euXZstO/XUU8tar1pGjhyZO+64Y4Uf4MePH5/dd989O+2008eqMXjw4CxatCjTpk1rWnbvvfeme/fuefjhh5sF6MmTJ6dPnz7ZeuutP1bNJGnXrl0mTJiQ2267Lb/5zW+alp900knp1q1bWX8mS6VSxo8fn4cffjhXXHFF0/IXXnghp512Wn7xi1+kV69eZav32c9+drnPH3Pnzs0VV1yRUqmUb33rW2WrVWut/nuS1jaLFi3Kddddl2nTpmXevHmZMGFCzjzzzIrVW/bXryTp3r17Tj/99Pzbv/1bXnvttWy66aZlrfWtb30rpVIpjzzySNZff/2m5Z/4xCdywgknlLVWcV5bbLFFdt1113zmM5/JfvvtlwkTJuQ///M/K1avGqpR71vf+lbat2+fadOmNXu/ttpqqxxyyCHx7QGt3/nnn5+zzjor1157bQ477LBat/Ox7Lrrrpk1a1ZuuOGGHH300UmSG264IX369Cn7HwiSZMiQIZk5c2bOPffcnH/++WUfv2jUqFHp2LFj/vznP2e99dZLkvTp0ye77LJLtt5663zve9/LZZddVtaaxX3IN7/5zdx444354x//mDPOOKNsNWo9r969e2fIkCG54447ct5555W1zvz583PvvfdmypQp2WeffZIkffv2zR577FHWOkma7es32GCDlEqlqv6+qZQvfOEL2XTTTTNhwoR8//vfb1q+aNGiTJo0qSyhfdttt02PHj0yZcqUfOYzn0ny4ZGQQw45JHfffXceeuihpiNKU6ZMKetRkAEDBmTcuHE56aSTsu++++aRRx7Jtddem0cffTQdO3YsW53kw5/1iy66KCeeeGIOOOCA9OvXLyNHjswBBxyQr3zlK2Wt1bFjx+V+/p555pmceuqpOfPMM/OlL32prPVqyZGkVuZ3v/tdtttuu2y77bY55phjctVVV1Xtw+iiRYvy61//Ov3798/GG29c1rHffPPN3HbbbRk1alSzD9zLlPsw/orsu+++2XnnnXPDDTdUvNaa7o033sif//znlb5fSSp22hHl8d3vfjc/+tGPcvPNN6/xAWmZE044IePHj296ftVVV+X444+vSK127drlnHPOyS9+8Yuynqryz958883cfvvt+da3vtUUJJbp3r17jj766Fx33XUV/z2w3nrr5f333y/beK1hXk8//XQeeOCBsn8gTZLOnTunc+fOuemmm1JfX1/28dcG7du3z7HHHpsJEyY0+zmYNGlSlixZkiOPPLIsdQYPHpzJkyc3PZ88eXIGDRqUffbZp2n5u+++m4cffrjsp4qddNJJ2XnnnfOVr3wlX/va1/LDH/4wO++8c1lrLHPcccdlv/32ywknnJCLL744Tz/9dLMjS5Uyf/78HHLIIRk0aFB+9KMfVbxeNQlJrcyVV16ZY445JsmHpw0sWLAgU6dOrVi9m2++uWln36VLl/zxj3/Mddddl3XWKe+PxsyZM9PY2JjtttuurOOuru222y6zZ88u+7jF7bjscc4555S9zsrqlfsvN8ver2233bbZ8k022aSp5ne/+92y1lzRNjzwwAPLWmNtceutt+b888/PH/7wh+y33361bqdsjjnmmNx333158cUX8+KLL+b+++9v2l9WwmGHHZZPfepTFTtdK0mee+65NDY2Zvvtt1/h69tvv33eeuutvPbaaxWp39jYmDvvvDO333579t1337KNW6t5LduPrLvuuvnkJz+Zf/zjH/nv//7vstZIPvyAP2HChFx99dXZcMMN87nPfS5nnnlmRa/pbYtOOOGEzJo1q9nnnPHjx2f48OHZYIMNylJj8ODBuf/++/PBBx/k7bffzhNPPJF99tkne++9d6ZMmZLkw2vB6+vryx6SSqVSLrvsstx1113ZfPPNc/rpp5d1/H/2y1/+Mk8//XS+/e1v55e//GXZzwj6Z0uXLs1RRx2V9u3b5ze/+U2b++Op0+1akRkzZuSRRx7JjTfemOTDnfARRxyRK6+8crkLDMtl8ODBTac7vPXWW7n00ktz4IEH5pFHHknfvn3LVqe1nJrV2NhYkf/Exe24TLdu3cpeZ2X1Vna0p9weeeSRLF26NEcffXTZ/3q6om348MMPV/RDcFu100475fXXX89ZZ52VPfbYI507d651S2Wx6aab5qCDDmr6y/NBBx2UTTbZpKI1zzvvvOy7774Vv+6j2vvIZWGioaGh6YNOOW8AsMy/mle5j/Is248sXrw4F154Ydq3b5/hw4eXtcYyw4cPz0EHHZR77703Dz30UNMfJ371q1+V9UYibdl2222Xz372s7nqqqsyaNCgzJw5M/fee2/OPvvsstUYNGhQFi9enEcffTRvvfVWBgwYkE033TT77LNPjj/++Lz33nuZMmVKttpqq4pc33jVVVelU6dOeeGFF/LKK6+kX79+Za+xzGabbZavf/3ruemmm6pyQ6IzzzwzDz74YB555JF06dKl4vWqzZGkVuTKK6/MBx98kJ49e6Z9+/Zp3759Lrvsslx//fVZsGBBRWquv/766d+/f/r3759Pf/rT+dWvfpXFixfn//7f/1vWOttss01KpVL+9re/lXXc1fXMM89U5PqF4nZc9qhkSPrnej169Cjr+P3790+pVMqMGTOaLd9qq63Sv3//5U6fKYcVbcMtttii7HXWBltssUWmTJmSv//97xk2bFjefvvtWrdUNieccELTX/DLfS3jiuy9994ZOnRoWa/VKVr2f+2ZZ55Z4evPPPNMNtpoo7L/RXjw4MGZPn16nnvuubz77ru5+uqry/rHllWZ16abblr2U62X7Ud23nnnXHXVVXn44Ydz5ZVXlrVG0brrrpv9998/P/jBD/LAAw9kxIgRFT3yWGldu3Zd4eeN+fPnl+3Izj8bOXJkrr/++rz99tsZP358tt5666brvMqhf//+6dWrVyZPnpzJkyc3jd2zZ8/07t07DzzwQCZPnlzWI6nLPPDAA7nwwgtz8803Z4899sjIkSMr/geRZZ8fK+3aa6/N//zP/+Taa6/NNttsU/F6tSAktRIffPBBrrnmmvz0pz/N9OnTmx5PPvlkevbsWdE7pRWVSqWss846ze4gVQ7dunXL0KFDc8kll2Tx4sXLvV7JWzwvc/fdd+cvf/lLxf6q2JZsvPHG2X///XPxxRev8P2i9evbt2+mTp2aefPmtamgNGzYsLz//vtpaGhY4S1vK2HcuHH505/+lAcffLDsYy/7v3bppZcut9+dN29efvOb3+SII44o+xHwZWGiT58+FflAtSrzqvTRlnXWWSdnnnlmvv/975f9d9rK7LDDDmv0PnPbbbfN448/vtzyxx9/PAMGDKhIzS9/+ctZZ511MnHixFxzzTU54YQTyv7zPnjw4EyZMiVTpkxpdmbO3nvvnVtvvTWPPPJI2U+1e+eddzJixIh885vfzODBg3PllVfmkUceyeWXX17WOrUwffr0jBw5MuPGjavafrgWhKRW4uabb85bb72VkSNHZscdd2z2GD58eMX+ElZfX5958+Zl3rx5eeaZZ3LSSSdl0aJFOfjgg8te65JLLsmSJUuyxx575Prrr89zzz2XZ555Jj//+c8zcODAstZaNq+///3vefzxx3POOefkkEMOyRe+8IUce+yxZa1VrFd8vP7662WvU02XXnppPvjgg+y+++657rrr8swzz2TGjBn59a9/nb/97W9p165drVvkX+jdu3emTJmSf/zjHxk6dGgWLlxYkToLFixo9sed6dOn5+WXX65IrXbt2uWZZ57J//f//X9V+xn85Cc/maOPPjo///nPKzL+xRdfnPr6+gwdOjT33HNPXn755dx2223Zf//9s8UWW+T//J//U5G6lfZR8xowYEB++MMfVryHL33pS2nXrl0uueSSso77xhtvZN99982vf/3rPPXUU3nhhRcyadKknH/++TnkkEPKWquavvnNb+bZZ5/Nf/3Xf+Wpp57KjBkzcsEFF+S3v/1txb5CoHPnzjniiCNyxhlnZO7cuRUJz4MHD859992X6dOnNztKtc8+++SKK67I+++/X/aQdMYZZ6SxsbHp+9b69euX//mf/8lpp51WkWujq+X111/PoYcemkGDBuWYY45Z7rNPpa6frAUhqZW48sorM2TIkBUezh4+fHimTZtWkQtCb7vttvTo0SM9evTInnvumUcffTSTJk2qyDVQW221VR5//PEMHjw43/nOd7Ljjjtm//33z1133VX228Aum1e/fv0ybNiwTJ48OT//+c/zhz/8oSIfrIrbcdljr732Knudatp6663zxBNPZMiQITnjjDOy8847Z/fdd88vfvGLnHrqqW3uLjbVsHTp0qqcBlHUq1evTJkyJa+//nrFgtKUKVOyyy67NHuMHTu27HWW6dq1a7p27Vqx8Vfk7LPPrsiXUScfno48bdq0bLXVVvnyl7+crbfeOl/72tcyePDgPPjggxU9dbeSttlmmzz66KNN8+rbt28OPPDADBgwIPfff39VrpVr3759TjzxxJx//vllPcLTuXPn7Lnnnrnwwguz9957Z8cdd8wPfvCDfPWrX83FF19ctjrVttVWW+Wee+7J3/72twwZMiR77rlnfve732XSpEkV+4L05MNT7t56660MHTo0PXv2LPv4gwcPzrvvvpv+/ftn8803b1q+zz775O233266VXi5TJ06NZdccknGjx+fTp06NS3/+te/ns9+9rNVOe2uUm655Za8+OKL+d///d/lPvf06NEjn/70p2vdYtmUGtfUdwlgDTNs2LD0799/jf4QBR/HWWedlQsuuCB33HFH0/fWALRG7m4HUGFvvfVW7r///kyZMiXf+MY3at0O1MzYsWPTr1+/PPTQQ9ljjz3K/nUTAOXiSBJAhR122GF59NFHc9xxx+XHP/5xm/suCQBoa4QkAACAAse5AQAACoQkAACAAiEJAACgQEgCAAAoEJIAAAAKhCQAWpURI0bk0EMPbdG/HTRoUL797W83WzZlypSUSqXMnz//Y/cGwNpBSAKAVdDY2JgPPvig1m0AUAVCEgBrjKeffjoHHnhgOnfunM033zxf+cpX8vrrryf58AjU1KlTc9FFF6VUKqVUKmX27NkZPHhwkmSjjTZKqVTKiBEjkiRLly7Nueeemy233DLrrbdedt555/z+979vqrXsCNStt96a3XbbLXV1dbnvvvvy5JNPZvDgwenSpUu6du2a3XbbLdOmTav6tgCgcoQkANYI8+fPz7777ptddtkl06ZNy2233ZZXX301X/7yl5MkF110UQYOHJivfvWrmTt3bubOnZvevXvn+uuvT5LMmDEjc+fOzUUXXZQkOffcc3PNNdfk8ssvz1//+teccsopOeaYYzJ16tRmdU8//fSMGzcuzzzzTHbaaaccffTR6dWrVx599NE89thjOf3009OhQ4fqbgwAKqp9rRsAgFVx8cUXZ5dddsk555zTtOyqq65K79698+yzz2bAgAHp2LFjOnXqlO7duzet061btyTJZpttlg033DBJUl9fn3POOSd33nlnBg4cmCTZaqutct999+WKK67IPvvs0/Tvzz777Oy///5Nz1966aX893//d7bbbrskyTbbbFOxOQNQG0ISAGuEJ598MpMnT07nzp2Xe23WrFkZMGDAKo81c+bMvPPOO83CT5K8//772WWXXZot23333Zs9Hz16dP7zP/8z/+///b8MGTIkX/rSl7L11luvxkwAaO2EJADWCIsWLcrBBx+c8847b7nXevTosdpjJcktt9ySLbbYotlrdXV1zZ6vv/76zZ6PGTMmRx11VG655ZbceuutOeuss3LttdfmsMMOW60eAGi9hCQA1gi77rprrr/++vTr1y/t26/411fHjh2zZMmS5ZYlabZ8hx12SF1dXV566aVmp9atqgEDBmTAgAE55ZRTcuSRR2b8+PFCEkAb4sYNALQ6CxYsyPTp05s9vva1r+XNN9/MkUcemUcffTSzZs3K7bffnuOPP74pAPXr1y8PP/xwZs+enddffz1Lly5N3759UyqVcvPNN+e1117LokWL0qVLl5x66qk55ZRTcvXVV2fWrFl5/PHH84tf/CJXX331Svt69913c+KJJ2bKlCl58cUXc//99+fRRx/N9ttvX61NA0AVOJIEQKszZcqU5a4NGjlyZO6///5897vfzQEHHJD6+vr07ds3w4YNyzrrfPg3v1NPPTXHHXdcdthhh7z77rt54YUX0q9fv4wdOzann356jj/++Bx77LGZMGFCfvSjH2XTTTfNueeem+effz4bbrhhdt1115x55pkr7atdu3Z54403cuyxx+bVV1/NJptsksMPPzxjx46t6PYAoLpKjY2NjbVuAgAAoLVwuh0AAECBkAQAAFAgJAEAABQISQAAAAVCEgAAQIGQBAAAUCAkAQAAFAhJAAAABUISAABAgZAEAABQICQBAAAUCEkAAAAF/z/V3kc6jihl/wAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "we can see no correlation of ctyphertext diagram to plaintext as vigenere creates great confusion\n", + "as plaintext nad key gets longer vigenere with random key converges to uniform distribution among all letters\n" + ] + } + ], + "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 Caesar’s 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": 109, + "id": "60fc029e-b140-4d6f-ae7d-c84fc7b4bf94", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "We cannot bruteforce the key as there is no correlation between it and ciphertext\n", + "plaintext length is only known variable (assuming it is not padded)\n", + "\n", + "We even can calculate key that produces any \"desired\" plaintext of the same length :)\n", + "\n", + "For example:\n", + "If we want to get plaintext \"cat\"\n", + "We can choose key = [113, 102, 158]\n", + "Enc(cat, [113, 102, 158]) = ['0b10010', '0b111', '0b11101010']\n", + "\n", + "If we want to get plaintext \"map\"\n", + "We can choose key = [127, 102, 154]\n", + "Enc(map, [127, 102, 154]) = ['0b10010', '0b111', '0b11101010']\n", + "\n", + "If we want to get plaintext \"lab\"\n", + "We can choose key = [126, 102, 136]\n", + "Enc(lab, [126, 102, 136]) = ['0b10010', '0b111', '0b11101010']\n", + "\n" + ] + } + ], + "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": 118, + "id": "720bab46-d467-491f-8715-cbd139ddb49b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "According to Historical-Ciphers.pdf:26\n", + "\n", + "\"If there are similar groups of (at least 3) letters in the ciphertext\n", + "Then the most probable explanation is that they correspond to\n", + "similar groups of letters in the plaintext Hence,\n", + "the difference in their positions in the text is divisible by m\"\n", + "\n", + "So i found that most common repeating n-grams are:\n", + "3-grams: [('FHK', 4)]\n", + "4-grams: [('DUWT', 2), ('KOAE', 2), ('GQGR', 2), ('VKOA', 2)]\n", + "5-grams: None\n", + "\n", + "We will try first 3-grams, as FHK is repeating 4 times\n", + "they are at indexes [0, 20, 55, 185]\n", + "so theyre relative spacing is [20, 35, 130]\n", + "with only common divisor [5]\n", + "its highly propable that key is [5] long, but lets look at the 4-grams to be sure\n", + "\n", + "Instances of KOAE are at indexes [81, 216]\n", + "so theyre relative spacing is [135] with divisors [3, 5, 9, 15, 27, 45, 135]\n", + "\n", + "Instances of KOAE are at indexes [85, 340]\n", + "so theyre relative spacing is [255] with divisors [3, 5, 15, 17, 51, 85, 255]\n", + "\n", + "Instances of KOAE are at indexes [303, 328]\n", + "so theyre relative spacing is [25] with divisors [5, 25]\n", + "\n", + "Instances of KOAE are at indexes [339, 409]\n", + "so theyre relative spacing is [70] with divisors [2, 5, 7, 10, 14, 35, 70]\n", + "\n", + "All of the them have relative spacing divisible by 5\n" + ] + } + ], + "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 propable 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": 126, + "id": "0571b097-c989-4a74-9dea-21a794a13490", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "For key length 2, average ic is 0.043\n", + "For key length 3, average ic is 0.041\n", + "For key length 4, average ic is 0.042\n", + "For key length 5, average ic is 0.059\n", + "For key length 6, average ic is 0.040\n", + "For key length 7, average ic is 0.041\n", + "For key length 8, average ic is 0.044\n", + "For key length 9, average ic is 0.043\n", + "For key length 10, average ic is 0.059\n", + "\n", + "Indices of coincidence being much closer to 0.065 (of english text) for 5 and 10 partila texts\n", + "supports that key should be multiple of five long\n" + ] + } + ], + "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": 137, + "id": "e3c37a9a-ee50-4550-869b-dbf49a244f79", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Assuming 5 long key, we can now express relations o partial keys as system of linear equations:\n", + "By measurring spikes in mutual index of coincidence between partial raw ciphertexts\n", + "and other randomly decrypted parts. We can spot how they relate and obtain:\n", + "C - A == 20 (mod 26)\n", + "D - A == 22 (mod 26)\n", + "D - B == 8 (mod 26)\n", + "E - B == 2 (mod 26)\n", + "E - D == 20 (mod 26)\n", + "and solve it as system of liner equations in Z26\n", + "resulting in key = [i, i + 14, i + 20, i + 22, i + 16] with i in {0..25}\n" + ] + } + ], + "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": 143, + "id": "335aaa6c-709b-4354-a0ee-d785cf9b4e99", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "now we can try spot meaningful text in every i:\n", + "for key: AOUWQ -> FTQSTAEFEPUPZFT\n", + "for key: BPVXR -> ESPRSZDEDOTOYES\n", + "for key: CQWYS -> DROQRYCDCNSNXDR\n", + "for key: DRXZT -> CQNPQXBCBMRMWCQ\n", + "for key: ESYAU -> BPMOPWABALQLVBP\n", + "for key: FTZBV -> AOLNOVZAZKPKUAO\n", + "for key: GUACW -> ZNKMNUYZYJOJTZN\n", + "for key: HVBDX -> YMJLMTXYXINISYM\n", + "for key: IWCEY -> XLIKLSWXWHMHRXL\n", + "for key: JXDFZ -> WKHJKRVWVGLGQWK\n", + "for key: KYEGA -> VJGIJQUVUFKFPVJ\n", + "for key: LZFHB -> UIFHIPTUTEJEOUI\n", + "for key: MAGIC -> THEGHOSTSDIDNTH\n", + "and here it is ^^^^^^^^^^^^^^^\n" + ] + } + ], + "source": [ + "print(\"now we can try spot meaningful text in every i:\")\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": 144, + "id": "ae19e142-6467-4c62-861c-32fa66365b3e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "THEGHOSTSDIDNTHELPEITHERITWASALWAYSANASTYSHOCKWHENONEOFTHEMGLIDEDSUDDENLYTHROUGHADOORYOUWERETRYINGTOOPENNEARLYHEADLESSNICKWASALWAYSHAPPYTOPOINTNEWGRYFFINDORSINTHERIGHTDIRECTIONBUTPEEVESTHEPOLTERGEISTWASWORTHTWOLOCKEDDOORSANDATRICKSTAIRCASEIFYOUMETHIMWHENYOUWERELATEFORCLASSHEWOULDDROPWASTEPAPERBASKETSONYOURHEADPULLRUGSFROMUNDERYOURFEETPELTYOUWITHBITSOFCHALKORSNEAKUPBEHINDYOUINVISIBLEGRABYOURNOSEANDSCREECHGOTYOURCONK\n" + ] + } + ], + "source": [ + "print( ''.join(vigenere_dec(ct, \"MAGIC\")) )" + ] + } + ], + "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 +}