diff options
Diffstat (limited to 'notebooks/python/cryptographic_primitives.ipynb')
| -rw-r--r-- | notebooks/python/cryptographic_primitives.ipynb | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/notebooks/python/cryptographic_primitives.ipynb b/notebooks/python/cryptographic_primitives.ipynb new file mode 100644 index 0000000..4d96549 --- /dev/null +++ b/notebooks/python/cryptographic_primitives.ipynb | |||
| @@ -0,0 +1,261 @@ | |||
| 1 | { | ||
| 2 | "cells": [ | ||
| 3 | { | ||
| 4 | "cell_type": "code", | ||
| 5 | "execution_count": 12, | ||
| 6 | "id": "fedd7d1a-36a9-4255-8305-a720dfb7b500", | ||
| 7 | "metadata": {}, | ||
| 8 | "outputs": [ | ||
| 9 | { | ||
| 10 | "name": "stdout", | ||
| 11 | "output_type": "stream", | ||
| 12 | "text": [ | ||
| 13 | "b\"{\\x8ea\\xf8wu!\\xeb\\xc4\\xc9\\xcb|\\\\\\xf1K\\xd5t\\x80i\\xbcD\\xb6!\\xfc\\xee\\x06lQ'V0\\xc6\"\n", | ||
| 14 | "7b8e61f8777521ebc4c9cb7c5cf14bd5748069bc44b621fcee066c51275630c6\n", | ||
| 15 | "b'e45h+Hd1IevEyct8XPFL1XSAabxEtiH87gZsUSdWMMY='\n" | ||
| 16 | ] | ||
| 17 | } | ||
| 18 | ], | ||
| 19 | "source": [ | ||
| 20 | "# hashes\n", | ||
| 21 | "\n", | ||
| 22 | "import base64\n", | ||
| 23 | "import hashlib\n", | ||
| 24 | "\n", | ||
| 25 | "hashed_result = hashlib.sha256(b\"This is a tesu\")\n", | ||
| 26 | "print(hashed_result.digest())\n", | ||
| 27 | "print(hashed_result.hexdigest())\n", | ||
| 28 | "print(base64.b64encode(hashed_result.digest()))\n" | ||
| 29 | ] | ||
| 30 | }, | ||
| 31 | { | ||
| 32 | "cell_type": "code", | ||
| 33 | "execution_count": 13, | ||
| 34 | "id": "9082c388-1c30-46dc-8897-15bf5db255c8", | ||
| 35 | "metadata": {}, | ||
| 36 | "outputs": [ | ||
| 37 | { | ||
| 38 | "name": "stdout", | ||
| 39 | "output_type": "stream", | ||
| 40 | "text": [ | ||
| 41 | "password_hash1 = 'pbkdf2_sha256$100000$j2jYEb1TvX/wh5JEAYGag6h+hRNTGe3rjyv5pegVBu4=$7cV7MljnJJYuGndge3jozwFP28aP8Gfj+QFGvv5bpfo='\n", | ||
| 42 | "password_hash2 = 'pbkdf2_sha256$100000$8+PsThJmJXNAbQ/zaR5AgRA/CuNv8L1uPUXRNuWHc9k=$Kf07CSCVQOTQrvWX5l/ZBdA7zsaEFH247QjUaewpxaU='\n" | ||
| 43 | ] | ||
| 44 | } | ||
| 45 | ], | ||
| 46 | "source": [ | ||
| 47 | "# PBKDF2\n", | ||
| 48 | "\n", | ||
| 49 | "import base64\n", | ||
| 50 | "import hashlib\n", | ||
| 51 | "import os\n", | ||
| 52 | "\n", | ||
| 53 | "\n", | ||
| 54 | "def get_password_hash(plaintext_password: str) -> str:\n", | ||
| 55 | " \"\"\"\n", | ||
| 56 | " Returns a complete password hash based on `plaintext_password`\n", | ||
| 57 | "\n", | ||
| 58 | " :return: The hashed version of `plaintext_password`\n", | ||
| 59 | " :rtype: str\n", | ||
| 60 | " \"\"\"\n", | ||
| 61 | " # parameters typycally sent as arguments or fetched from config\n", | ||
| 62 | " hash_algo = \"sha256\" # the hash algorithm to use for HMAC\n", | ||
| 63 | " iterations = 100000 # amount of iterations\n", | ||
| 64 | "\n", | ||
| 65 | " salt = os.urandom(32) # 32 bytes of random data\n", | ||
| 66 | "\n", | ||
| 67 | " key = hashlib.pbkdf2_hmac(hash_algo, plaintext_password.encode(), salt, iterations)\n", | ||
| 68 | "\n", | ||
| 69 | " return (\n", | ||
| 70 | " f\"pbkdf2_{hash_algo}${iterations}$\"\n", | ||
| 71 | " f\"{base64.b64encode(salt).decode(\"ascii\")}$\"\n", | ||
| 72 | " f\"{base64.b64encode(key).decode(\"ascii\")}\"\n", | ||
| 73 | " )\n", | ||
| 74 | "\n", | ||
| 75 | "\n", | ||
| 76 | "def password_matches(plaintext_password: str, password_hash: str) -> bool:\n", | ||
| 77 | " \"\"\"Returns True if `plaintext_password` matches `password_hash`, False otherwise\"\"\"\n", | ||
| 78 | " hash_tokens = password_hash.split(\"$\")\n", | ||
| 79 | " hash_algo = hash_tokens[0].split(\"_\")[1]\n", | ||
| 80 | " iterations = int(hash_tokens[1])\n", | ||
| 81 | " salt = base64.b64decode(hash_tokens[2])\n", | ||
| 82 | " key = hashlib.pbkdf2_hmac(hash_algo, plaintext_password.encode(), salt, iterations)\n", | ||
| 83 | " return key == base64.b64decode(hash_tokens[3])\n", | ||
| 84 | "\n", | ||
| 85 | "\n", | ||
| 86 | "password_hash1 = get_password_hash(\"didn't expect a Spanish Inquisition!\")\n", | ||
| 87 | "password_hash2 = get_password_hash(\"didn't expect a Spanish Inquisition!\")\n", | ||
| 88 | "print(f\"{password_hash1 = }\")\n", | ||
| 89 | "print(f\"{password_hash2 = }\")\n", | ||
| 90 | "\n", | ||
| 91 | "# print(password_matches(\"didn't expect a Spanish Inquisition!\", 'pbkdf2_sha256$100000$JfwrS72mFimypbwhJ/NCyds2dkXtknvfWM5AM3+Z5GQ=$k8YA8csSJZhuYW0um7utq3+lSX5pTAQPd9dukObWrPo='))" | ||
| 92 | ] | ||
| 93 | }, | ||
| 94 | { | ||
| 95 | "cell_type": "code", | ||
| 96 | "execution_count": 14, | ||
| 97 | "id": "1274325e-f670-4af8-a0bc-2ff7f668f769", | ||
| 98 | "metadata": {}, | ||
| 99 | "outputs": [ | ||
| 100 | { | ||
| 101 | "name": "stdout", | ||
| 102 | "output_type": "stream", | ||
| 103 | "text": [ | ||
| 104 | "('gxk9W0XFgbzbjRWB7zCPjbWs3', '00000bf5db2c3c6484904bf5e8314aeb123f5594af1f283fb62e14c7d196e84c')\n" | ||
| 105 | ] | ||
| 106 | } | ||
| 107 | ], | ||
| 108 | "source": [ | ||
| 109 | "# Proof of work\n", | ||
| 110 | "\n", | ||
| 111 | "import hashlib\n", | ||
| 112 | "import random\n", | ||
| 113 | "import string\n", | ||
| 114 | "\n", | ||
| 115 | "VALID_CHARS = string.ascii_letters + string.digits # valid characters for response\n", | ||
| 116 | "\n", | ||
| 117 | "\n", | ||
| 118 | "def pow_solve_rnd(challenge: str, starting_chars: str = \"00000\", answer_size: int = 25) -> tuple:\n", | ||
| 119 | " \"\"\"\n", | ||
| 120 | " Solves a POW challenge for SHA-256 using `random.choice`\n", | ||
| 121 | "\n", | ||
| 122 | " :return: (response, SHA-256 digest) tuple that solves the challenge\n", | ||
| 123 | " :rtype: tuple\n", | ||
| 124 | " \"\"\"\n", | ||
| 125 | " while True:\n", | ||
| 126 | " attempt = ''.join(\n", | ||
| 127 | " [random.choice(VALID_CHARS) for x in range(answer_size)])\n", | ||
| 128 | " dig = hashlib.sha256(('{0}:{1}'.format(challenge, attempt)).encode()).hexdigest()\n", | ||
| 129 | " if dig.startswith(starting_chars):\n", | ||
| 130 | " return attempt, dig\n", | ||
| 131 | "\n", | ||
| 132 | "print(f\"{pow_solve_rnd('2024-08-07')}\")" | ||
| 133 | ] | ||
| 134 | }, | ||
| 135 | { | ||
| 136 | "cell_type": "code", | ||
| 137 | "execution_count": 25, | ||
| 138 | "id": "6752c055-7e49-46c4-858b-599d069a7e77", | ||
| 139 | "metadata": {}, | ||
| 140 | "outputs": [ | ||
| 141 | { | ||
| 142 | "name": "stdout", | ||
| 143 | "output_type": "stream", | ||
| 144 | "text": [ | ||
| 145 | "enc-val$2$6ppVDmiJi0P9BnHuzwFUPA+vV/Gsb4INe2O9E/Ma2uI=$6879D8rw8645nbQ/NQtY3Q+AwKTWpd1P6Q==\n", | ||
| 146 | "Invalid tag when decrypting enc-val$2$tmuH3MTDQ9vMbxJ1pZUb0rQlF43Lgr/AYfkA+vSOZs0=$5CVt3VrxhuuOOVt1vCXeGpbhNh/20IcFNg==\n", | ||
| 147 | "None\n" | ||
| 148 | ] | ||
| 149 | } | ||
| 150 | ], | ||
| 151 | "source": [ | ||
| 152 | "# Encryption / decryption using AES-256-GCM\n", | ||
| 153 | "\n", | ||
| 154 | "import hashlib\n", | ||
| 155 | "import os\n", | ||
| 156 | "\n", | ||
| 157 | "from cryptography.exceptions import InvalidTag\n", | ||
| 158 | "from cryptography.hazmat.primitives.ciphers.aead import AESGCM\n", | ||
| 159 | "\n", | ||
| 160 | "\n", | ||
| 161 | "def encrypt(password: str, data: str) -> str:\n", | ||
| 162 | " \"\"\"\n", | ||
| 163 | " Encrypts `data` using `password` and AES-256-GCM.\n", | ||
| 164 | "\n", | ||
| 165 | " The output string is in the following format:\n", | ||
| 166 | " enc-val$`version-num`$`bas64-salt`$`base64-encrypted_data`\n", | ||
| 167 | "\n", | ||
| 168 | " :param password: The password to generate the key with\n", | ||
| 169 | " :type password: str\n", | ||
| 170 | "\n", | ||
| 171 | " :param data: The data to be encrypted\n", | ||
| 172 | " :type data: str\n", | ||
| 173 | "\n", | ||
| 174 | " :return: The output string\n", | ||
| 175 | " :rtype: str\n", | ||
| 176 | " \"\"\"\n", | ||
| 177 | " data_bytes = data.encode()\n", | ||
| 178 | " salt = os.urandom(32)\n", | ||
| 179 | " aesgcm = AESGCM(\n", | ||
| 180 | " hashlib.scrypt(\n", | ||
| 181 | " password.encode(), salt=salt, n=2**14, r=8, p=1, dklen=32\n", | ||
| 182 | " )\n", | ||
| 183 | " )\n", | ||
| 184 | " nonce = salt[:12]\n", | ||
| 185 | " # padding_length_bytes = b\"-1\" # no padding used 2 bytes \"sign\"\n", | ||
| 186 | " edata = aesgcm.encrypt(\n", | ||
| 187 | " nonce, data_bytes, salt\n", | ||
| 188 | " )\n", | ||
| 189 | " return (\n", | ||
| 190 | " f\"enc-val$2${base64.b64encode(salt).decode()}${base64.b64encode(edata).decode()}\"\n", | ||
| 191 | " )\n", | ||
| 192 | "\n", | ||
| 193 | "\n", | ||
| 194 | "def decrypt(password: str, edata: str) -> str:\n", | ||
| 195 | " \"\"\"\n", | ||
| 196 | " Decrypts `edata` using `password`.\n", | ||
| 197 | "\n", | ||
| 198 | " `edata` is in the following format:\n", | ||
| 199 | " enc-val$`version-num`$`bas64-salt`$`base64-encrypted_data`\n", | ||
| 200 | "\n", | ||
| 201 | " :param password: The password to generate the key with\n", | ||
| 202 | " :type password: str\n", | ||
| 203 | "\n", | ||
| 204 | " :param edata: The data to be decrypted\n", | ||
| 205 | " :type edata: str\n", | ||
| 206 | "\n", | ||
| 207 | " :return: The output string / decrypted data\n", | ||
| 208 | " :rtype: str\n", | ||
| 209 | " \"\"\"\n", | ||
| 210 | " # check for supported versions first...\n", | ||
| 211 | "\n", | ||
| 212 | " try:\n", | ||
| 213 | " salt, data = (base64.b64decode(t) for t in edata[10:].split('$'))\n", | ||
| 214 | " nonce = salt[:12]\n", | ||
| 215 | " aesgcm = AESGCM(\n", | ||
| 216 | " hashlib.scrypt(\n", | ||
| 217 | " password.encode(), salt=salt, n=2**14, r=8, p=1, dklen=32\n", | ||
| 218 | " )\n", | ||
| 219 | " )\n", | ||
| 220 | "\n", | ||
| 221 | " # decrypt\n", | ||
| 222 | " data = aesgcm.decrypt(nonce, data, salt)\n", | ||
| 223 | " return data.decode()\n", | ||
| 224 | " except InvalidTag:\n", | ||
| 225 | " print(f\"Invalid tag when decrypting {edata}\")\n", | ||
| 226 | "\n", | ||
| 227 | "print(f\"{encrypt('my passphrase', 'my secret')}\")\n", | ||
| 228 | "print(f\"{decrypt('my passphrase', 'enc-val$2$tmuH3MTDQ9vMbxJ1pZUb0rQlF43Lgr/AYfkA+vSOZs0=$5CVt3VrxhuuOOVt1vCXeGpbhNh/20IcFNg==')}\")" | ||
| 229 | ] | ||
| 230 | }, | ||
| 231 | { | ||
| 232 | "cell_type": "code", | ||
| 233 | "execution_count": null, | ||
| 234 | "id": "8ccfb453-d3fd-4383-a01d-f81b19f2cb82", | ||
| 235 | "metadata": {}, | ||
| 236 | "outputs": [], | ||
| 237 | "source": [] | ||
| 238 | } | ||
| 239 | ], | ||
| 240 | "metadata": { | ||
| 241 | "kernelspec": { | ||
| 242 | "display_name": "Python 3 (ipykernel)", | ||
| 243 | "language": "python", | ||
| 244 | "name": "python3" | ||
| 245 | }, | ||
| 246 | "language_info": { | ||
| 247 | "codemirror_mode": { | ||
| 248 | "name": "ipython", | ||
| 249 | "version": 3 | ||
| 250 | }, | ||
| 251 | "file_extension": ".py", | ||
| 252 | "mimetype": "text/x-python", | ||
| 253 | "name": "python", | ||
| 254 | "nbconvert_exporter": "python", | ||
| 255 | "pygments_lexer": "ipython3", | ||
| 256 | "version": "3.13.9" | ||
| 257 | } | ||
| 258 | }, | ||
| 259 | "nbformat": 4, | ||
| 260 | "nbformat_minor": 5 | ||
| 261 | } | ||
