Last active
April 17, 2026 15:31
-
-
Save J08nY/be199b59fc71241cf50900791775e402 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "id": "3bb2a3ef", | |
| "metadata": {}, | |
| "source": [ | |
| "# Tutorial 1: Cryptography in practice\n", | |
| "\n", | |
| "# Lab Content\n", | |
| "\n", | |
| "In this tutorial, we will explore how abstract cryptographic operations are implemented in software. Our goal is to identify the \"critical paths\" where secret data (keys) interact with the algorithm. Note that hardware-focused implementations and implementations in hardware may look differently.\n", | |
| "\n", | |
| "## Tasks\n", | |
| "1. Implement core components of AES, RSA, and ECC.\n", | |
| "2. Find and examine components of open-source implementations.\n", | |
| "\n", | |
| "## Task 1: AES-128 - The S-Box and Key Addition\n", | |
| "\n", | |
| "Your task is to implement the SBOX lookup table, the `sub_bytes` and `add_round_key` functions. The test at the end of the cell should pass.\n", | |
| "\n", | |
| "Consult the [AES standard](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf) and [Wikipedia](https://en.wikipedia.org/wiki/Rijndael_S-box).\n", | |
| "\n", | |
| "**Note**: The solution is available in the [aes](aes.py) module. You can look at it if you are struggling." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "0e350810", | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Standard AES S-Box\n", | |
| "# Task 1: Find the AES SBOX table (256 bytes) and paste it here.\n", | |
| "SBOX = []\n", | |
| "\n", | |
| "def sub_bytes(state: list) -> list:\n", | |
| " \"\"\"\n", | |
| " The SubBytes step of AES.\n", | |
| " \"\"\"\n", | |
| " # Task 1: Implement the SubBytes step here\n", | |
| " raise NotImplementedError\n", | |
| "\n", | |
| "def add_round_key(state: list, round_key: list) -> list:\n", | |
| " \"\"\"\n", | |
| " The AddRoundKey step of AES.\n", | |
| " \"\"\"\n", | |
| " # Task 1: Implement the AddRoundKey step here\n", | |
| " raise NotImplementedError\n", | |
| "\n", | |
| "def shift_rows(state: list) -> list:\n", | |
| " \"\"\"\n", | |
| " The ShiftRows step of AES.\n", | |
| " \"\"\"\n", | |
| " return [state[0], state[5], state[10], state[15], state[4], state[9], state[14], state[3], state[8], state[13], state[2], state[7], state[12], state[1], state[6], state[11]]\n", | |
| "\n", | |
| "def xtime(a: int) -> int:\n", | |
| " return (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)\n", | |
| "\n", | |
| "def mix_columns(state: list) -> list:\n", | |
| " \"\"\"\n", | |
| " The MixColumns step of AES.\n", | |
| " \"\"\"\n", | |
| " res = [0] * 16\n", | |
| " for i in range(4):\n", | |
| " c = state[i*4 : i*4+4]\n", | |
| " res[i*4+0] = xtime(c[0]) ^ (xtime(c[1]) ^ c[1]) ^ c[2] ^ c[3]\n", | |
| " res[i*4+1] = c[0] ^ xtime(c[1]) ^ (xtime(c[2]) ^ c[2]) ^ c[3]\n", | |
| " res[i*4+2] = c[0] ^ c[1] ^ xtime(c[2]) ^ (xtime(c[3]) ^ c[3])\n", | |
| " res[i*4+3] = (xtime(c[0]) ^ c[0]) ^ c[1] ^ c[2] ^ xtime(c[3])\n", | |
| " return res\n", | |
| "\n", | |
| "def expand_key(key: bytes) -> list:\n", | |
| " \"\"\"\n", | |
| " AES-128 Key Expansion.\n", | |
| " \"\"\"\n", | |
| " Rcon = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36]\n", | |
| " # Put the key into a grid\n", | |
| " w = [list(key[i:i+4]) for i in range(0, 16, 4)]\n", | |
| " for i in range(4, 44):\n", | |
| " temp = list(w[i-1])\n", | |
| " if i % 4 == 0:\n", | |
| " temp = [SBOX[b] for b in temp[1:] + temp[:1]]\n", | |
| " temp[0] ^= Rcon[i//4]\n", | |
| " w.append([w[i-4][j] ^ temp[j] for j in range(4)])\n", | |
| " return [sum(w[i:i+4], []) for i in range(0, 44, 4)]\n", | |
| "\n", | |
| "def aes_encrypt(plaintext: bytes, key: bytes):\n", | |
| " \"\"\"\n", | |
| " An AES-128 encryption loop.\n", | |
| " \"\"\"\n", | |
| " round_keys = expand_key(key)\n", | |
| " state = list(plaintext)\n", | |
| " \n", | |
| " # Initial Round\n", | |
| " state = add_round_key(state, round_keys[0])\n", | |
| " \n", | |
| " # Rounds 1 to 9\n", | |
| " for i in range(1, 10):\n", | |
| " state = sub_bytes(state)\n", | |
| " state = shift_rows(state)\n", | |
| " state = mix_columns(state)\n", | |
| " state = add_round_key(state, round_keys[i])\n", | |
| " \n", | |
| " # Final Round (No MixColumns)\n", | |
| " state = sub_bytes(state)\n", | |
| " state = shift_rows(state)\n", | |
| " state = add_round_key(state, round_keys[10])\n", | |
| " \n", | |
| " return bytes(state)\n", | |
| "\n", | |
| "\n", | |
| "# --- TEST ---\n", | |
| "key = bytes.fromhex(\"2b7e151628aed2a6abf7158809cf4f3c\")\n", | |
| "pt = bytes.fromhex(\"6bc1bee22e409f96e93d7e117393172a\")\n", | |
| "expected_ct = \"3ad77bb40d7a3660a89ecaf32466ef97\"\n", | |
| "\n", | |
| "try:\n", | |
| " if not SBOX:\n", | |
| " raise ValueError(\"Empty SBOX\")\n", | |
| " ct = aes_encrypt(pt, key)\n", | |
| " print(f\"Plaintext: {pt.hex()}\")\n", | |
| " print(f\"Ciphertext: {ct.hex()}\")\n", | |
| " print(f\"Success: {ct.hex() == expected_ct}\")\n", | |
| "except Exception as e:\n", | |
| " print(f\"Encryption failed: {e}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "e575a5ed", | |
| "metadata": {}, | |
| "source": [ | |
| "## Task 2: RSA - Modular exponentiation\n", | |
| "\n", | |
| "The core operation in RSA is modular exponentiation. There are many ways to implement it. What they have in common is that they process the private key in some rgular fashion (i.e., left-to-right, right-to-left, in \"windows\", as a \"comb\", or sliding).\n", | |
| "\n", | |
| "Your task is to implement this algorithm. You can choose whichever algorithm you want, it just has to be correct. I suggest a simple one.\n", | |
| "\n", | |
| "Consult [Exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring), examine the [mod](mod.py) file for how the Mod class works. It represent an integer modulo a given $n$.\n", | |
| "\n", | |
| "**Note**: The solution is available in the [rsa](rsa.py) module. You can look at it if you are struggling." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "6ffa9874", | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "import secrets\n", | |
| "from mod import Mod\n", | |
| "\n", | |
| "def square_and_multiply(base: int, exponent: int, modulus: int) -> int:\n", | |
| " \"\"\"\n", | |
| " Computes (base**exponent) % modulus using square-and-multiply.\n", | |
| " \"\"\"\n", | |
| " # Task 2: Implement square and multiply.\n", | |
| " raise NotImplementedError\n", | |
| "\n", | |
| "def pkcs1_v15_pad(message: bytes, modulus_len: int) -> int:\n", | |
| " \"\"\"\n", | |
| " A PKCS#1 v1.5 padding scheme (for encryption).\n", | |
| " Structure: 0x00 || 0x02 || PS || 0x00 || Message\n", | |
| " \n", | |
| "\t\thttps://www.rfc-editor.org/rfc/rfc8017#section-7.2.1\n", | |
| " \n", | |
| " Warning: This is not a good padding scheme! It is vulnerable\n", | |
| " to Bleichenbacher's attack (see lecture 5 of Real World Crypto Engineering).\n", | |
| " \"\"\"\n", | |
| " if len(message) > modulus_len - 11:\n", | |
| " raise ValueError(\"Message too long for modulus\")\n", | |
| " \n", | |
| " # Padding string (non-zero random bytes)\n", | |
| " ps_len = modulus_len - len(message) - 3\n", | |
| " ps = b\"\"\n", | |
| " while len(ps) < ps_len:\n", | |
| " new_byte = secrets.token_bytes(1)\n", | |
| " if new_byte != b\"\\x00\":\n", | |
| " ps += new_byte\n", | |
| " \n", | |
| " padded = b\"\\x00\\x02\" + ps + b\"\\x00\" + message\n", | |
| " return int.from_bytes(padded, \"big\")\n", | |
| "\n", | |
| "def pkcs1_v15_unpad(padded_int: int, modulus_len: int) -> bytes:\n", | |
| " \"\"\"\n", | |
| " Removes the PKCS#1 v1.5 padding (for decryption).\n", | |
| "\t \n", | |
| "\t https://www.rfc-editor.org/rfc/rfc8017#section-7.2.2\n", | |
| " \n", | |
| " Warning: This is not a good padding scheme! It is vulnerable\n", | |
| " to Bleichenbacher's attack (see lecture 5 of Real World Crypto Engineering).\n", | |
| " \"\"\"\n", | |
| " padded = padded_int.to_bytes(modulus_len, \"big\")\n", | |
| " print(padded)\n", | |
| " if padded[0:2] != b\"\\x00\\x02\":\n", | |
| " raise ValueError(\"Invalid padding prefix\")\n", | |
| " \n", | |
| " # Find the separator\n", | |
| " sep_idx = padded.find(b\"\\x00\", 2)\n", | |
| " if sep_idx == -1:\n", | |
| " raise ValueError(\"No separator found\")\n", | |
| " \n", | |
| " return padded[sep_idx + 1:]\n", | |
| "\n", | |
| "def rsa_encrypt(m: bytes, e: int, n: int) -> int:\n", | |
| " \"\"\"\n", | |
| " RSA Encryption with padding.\n", | |
| " \"\"\"\n", | |
| " mod_len = (n.bit_length() + 7) // 8\n", | |
| " padded_m = pkcs1_v15_pad(m, mod_len)\n", | |
| " return square_and_multiply(padded_m, e, n)\n", | |
| "\n", | |
| "def rsa_decrypt(c: int, d: int, n: int) -> bytes:\n", | |
| " \"\"\"\n", | |
| " RSA Decryption and unpadding.\n", | |
| " \"\"\"\n", | |
| " mod_len = (n.bit_length() + 7) // 8\n", | |
| " padded_m = square_and_multiply(c, d, n)\n", | |
| " return pkcs1_v15_unpad(padded_m, mod_len)\n", | |
| "\n", | |
| "\n", | |
| "# --- TEST ---\n", | |
| "p = 0x7c644070a1b9b3155ae937f9688144299d857299d5fb47bb7ac5b55aaa684b151c4b4b415f8256d655febb6991a8877fb6c00b6a67a7a9c83f3580d4d333eac99541fdaa951ce38d37abd857d94f034a97666ae1899e4dca933b79ab3d304ee310bcef35a85e889ce1808497f5d9f5f257fdebe6b8aada36f06f674b7f81121b\n", | |
| "q = 0x9cef0fe8c6a49c2a6df5ca3042bcf01e0226847bac81d8656c2203b6486cbcf955c70a224395a5e5bc7693a5b2fdcd38479a3265c4b4898bb1c9228b1d8f1dfd2398f9572f2193d759e976211cd68bfd3f927f2279979d17c7248c199b0aa60b8dce47285e9b098b1a0fe59844b60d74389f477400a131e404866c8ecac9368b\n", | |
| "n = p * q\n", | |
| "e = 17\n", | |
| "d = 0xd74ed47f5ea5a6903da8bb3f47ef76578627a2f72c1eee2ebf28572cf0ebb10b4df8445afd38255d465d98eebffea614d7cd821219c7770934c49ae87ee7597bb1b36e687123e181d60f6dcb0a15a6a0adafb37fe2188730cfc5884869a7172042772b31065f61f87728ea0332032a3d522df377368e357764a9eddff6d11afa6a611e06c4e97951515c9e0a83d731faa8298db0f954b59e5ad4a3e1bb0f3fc7aea9c310dfd6104242b1616507ee42aba9b3ae24a15e9e52236e2631adb35fa60ebd4602da92a3af6a8649ddf870e8166c8cf9e5c7d9f0d69b7bfe06c8b205859c2bb103aa989f4136eb605be5cd756f64a25a9cca4470df8dd743b00e8563d\n", | |
| "msg = b\"Hi!\"\n", | |
| "\n", | |
| "try:\n", | |
| " cipher = rsa_encrypt(msg, e, n)\n", | |
| " decrypted = rsa_decrypt(cipher, d, n)\n", | |
| " print(f\"Original: {msg}\")\n", | |
| " print(f\"Encrypted: {cipher}\")\n", | |
| " print(f\"Decrypted: {decrypted}\")\n", | |
| " print(f\"Success: {msg == decrypted}\")\n", | |
| "except NotImplementedError:\n", | |
| " print(\"Implement square_and_multiply.\")\n", | |
| "except Exception as e:\n", | |
| " print(f\"RSA failed: {e}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "0bfd88ee", | |
| "metadata": {}, | |
| "source": [ | |
| "## Task 3: ECC - Scalar Multiplication\n", | |
| "\n", | |
| "Elliptic Curve Cryptography uses a similar loop structure, but the operations are point addition and point doubling. Again, there are many algorithms for this.\n", | |
| "\n", | |
| "Consult [Scalar multiplication](https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication) and an excellent [scalar multiplication](https://cryptojedi.org/peter/data/eccss-20130911b.pdf) talk by Peter Schwabe, also examine the [mod](mod.py) file for how the Mod class works. It represent an integer modulo a given $n$.\n", | |
| "\n", | |
| "**Note**: The solution is available in the [ecc](ecc.py) module. You can look at it if you are struggling." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "id": "4cf2c768", | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "from dataclasses import dataclass\n", | |
| "from hashlib import sha1\n", | |
| "from typing import Tuple, Union\n", | |
| "\n", | |
| "from mod import Mod\n", | |
| "\n", | |
| "\n", | |
| "@dataclass\n", | |
| "class Point(object):\n", | |
| " \"\"\"A point on an elliptic curve.\"\"\"\n", | |
| " x: Mod\n", | |
| " y: Mod\n", | |
| "\n", | |
| "\n", | |
| "@dataclass\n", | |
| "class Curve(object):\n", | |
| " \"\"\"An elliptic curve.\"\"\"\n", | |
| "\n", | |
| " p: int\n", | |
| " a: Mod\n", | |
| " b: Mod\n", | |
| " g: Point\n", | |
| " n: int\n", | |
| "\n", | |
| " def __init__(self, p: int, a: int, b: int, gx: int, gy: int, n: int):\n", | |
| " self.p = p\n", | |
| " self.a = Mod(a, p)\n", | |
| " self.b = Mod(b, p)\n", | |
| " self.g = Point(Mod(gx, p), Mod(gy, p))\n", | |
| " self.n = n\n", | |
| "\n", | |
| " def add(self, p: Point, q: Point):\n", | |
| " \"\"\"\n", | |
| " Add two points on an elliptic curve (If the points are equal, the result is erroneous).\n", | |
| " See <https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Point_addition>.\n", | |
| " \"\"\"\n", | |
| " λ = (q.y - p.y) / (q.x - p.x)\n", | |
| " x = λ**2 - p.x - q.x\n", | |
| " y = λ * (p.x - x) - p.y\n", | |
| " return Point(x, y)\n", | |
| "\n", | |
| " def dbl(self, p: Point):\n", | |
| " \"\"\"\n", | |
| " Double a point on an elliptic curve.\n", | |
| " See <https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Point_doubling>.\n", | |
| " \"\"\"\n", | |
| " λ = (3 * p.x**2 + self.a) / (2 * p.y)\n", | |
| " x = λ**2 - p.x - p.x\n", | |
| " y = λ * (p.x - x) - p.y\n", | |
| " return Point(x, y)\n", | |
| "\n", | |
| "\n", | |
| "# A popular curve: https://std.neuromancer.sk/secg/secp256r1\n", | |
| "curve_secp256r1 = Curve(\n", | |
| " 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF,\n", | |
| " 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC,\n", | |
| " 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B,\n", | |
| " 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296,\n", | |
| " 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5,\n", | |
| " 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551,\n", | |
| ")\n", | |
| "\n", | |
| "\n", | |
| "def scalarmult(point: Point, scalar: Union[int, Mod], curve: Curve) -> Point:\n", | |
| " \"\"\"\n", | |
| " Perform scalar multiplication of the `point` with the `scalar` on the elliptic curve given by `curve`.\n", | |
| " See <https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication> for some methods.\n", | |
| " Returns `[scalar]point`.\n", | |
| " \"\"\"\n", | |
| " s = int(scalar)\n", | |
| " # Task 3: Implement the scalar multiplication algorithm here.\n", | |
| " raise NotImplementedError\n", | |
| "\n", | |
| "def keygen(curve: Curve) -> Tuple[Mod, Point]:\n", | |
| " \"\"\"\n", | |
| " Generate an ECC keypair on the `curve`.\n", | |
| " Returns a tuple of `private key, public key`.\n", | |
| " \"\"\"\n", | |
| " private = Mod.random(curve.n)\n", | |
| " public = scalarmult(curve.g, int(private), curve)\n", | |
| " return private, public\n", | |
| "\n", | |
| "def sign(message: bytes, private: Mod, curve: Curve) -> Tuple[Mod, Mod]:\n", | |
| " \"\"\"\n", | |
| " Sign the `message` using the `private` key on the `curve`.\n", | |
| " Returns the signature tuple `r, s`.\n", | |
| "\n", | |
| " https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm\n", | |
| " \"\"\"\n", | |
| " # h = SHA1(message) and then the ECDSA trimming.\n", | |
| " h = int(sha1(message).hexdigest(), 16) >> (\n", | |
| " 0 if 160 <= curve.n.bit_length() else 160 - curve.n.bit_length()\n", | |
| " )\n", | |
| " # k = random integer from ℤₙ, the Mod.random function should be considered constant-time.\n", | |
| " k = Mod.random(curve.n)\n", | |
| " # r = ([k]G)_x mod n\n", | |
| " r = Mod(int(scalarmult(curve.g, int(k), curve).x), curve.n)\n", | |
| " # s = k^-1 * (H(message) + r * x) mod n\n", | |
| " s = k ** (-1) * (h + r * private)\n", | |
| " return r, s\n", | |
| "\n", | |
| "def verify(signature: Tuple[Mod, Mod], message: bytes, public: Point, curve: Curve) -> bool:\n", | |
| " \"\"\"\n", | |
| " Verify the `signature` on the `message` using the `public` key on the `curve`.\n", | |
| "\n", | |
| " Note, this is missing some checks.\n", | |
| "\n", | |
| " https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm\n", | |
| " \"\"\"\n", | |
| " # h = SHA1(message) and then the ECDSA trimming.\n", | |
| " h = int(sha1(message).hexdigest(), 16) >> (\n", | |
| " 0 if 160 <= curve.n.bit_length() else 160 - curve.n.bit_length()\n", | |
| " )\n", | |
| " r, s = signature\n", | |
| " if r == 0 or s == 0:\n", | |
| " raise ValueError(\"Malformed signature\")\n", | |
| " sinv = s ** (-1)\n", | |
| " u1 = h * sinv\n", | |
| " u2 = r * sinv\n", | |
| " u1G = scalarmult(curve.g, u1, curve)\n", | |
| " u2P = scalarmult(public, u2, curve)\n", | |
| " res = curve.add(u1G, u2P)\n", | |
| " return int(r) == int(res.x)\n", | |
| "\n", | |
| "\n", | |
| "# --- TEST ---\n", | |
| "message = b\"Hi!\"\n", | |
| "\n", | |
| "try:\n", | |
| " privkey = Mod(0xd836c8d5317e144e95f2fd225c75c7eb8f3e386514342dbaf4d89ffd0cc5b0ba, curve_secp256r1.n)\n", | |
| " pubkey = scalarmult(curve_secp256r1.g, privkey, curve_secp256r1)\n", | |
| " \n", | |
| " signature = sign(message, privkey, curve_secp256r1)\n", | |
| " verfied = verify(signature, message, pubkey, curve_secp256r1)\n", | |
| "\n", | |
| " print(f\"Message = {message}\")\n", | |
| " print(f\"Signature = {signature}\")\n", | |
| " print(f\"Verified = {verfied}\")\n", | |
| "except NotImplementedError:\n", | |
| " print(\"Implement scalarmult.\")\n", | |
| "except Exception as e:\n", | |
| " print(f\"ECDSA failed: {e}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "id": "b13c2c8f", | |
| "metadata": {}, | |
| "source": [ | |
| "## Examining open-source crypto libraries\n", | |
| "\n", | |
| "See the slides for the next steps." | |
| ] | |
| } | |
| ], | |
| "metadata": { | |
| "jupytext": { | |
| "formats": "ipynb,md" | |
| }, | |
| "kernelspec": { | |
| "display_name": "Python 3 (ipykernel)", | |
| "language": "python", | |
| "name": "python3" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 5 | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Task 1: Find the AES SBOX table (256 bytes) and paste it here. | |
| SBOX = [ | |
| 0x63, | |
| 0x7C, | |
| 0x77, | |
| 0x7B, | |
| 0xF2, | |
| 0x6B, | |
| 0x6F, | |
| 0xC5, | |
| 0x30, | |
| 0x01, | |
| 0x67, | |
| 0x2B, | |
| 0xFE, | |
| 0xD7, | |
| 0xAB, | |
| 0x76, | |
| 0xCA, | |
| 0x82, | |
| 0xC9, | |
| 0x7D, | |
| 0xFA, | |
| 0x59, | |
| 0x47, | |
| 0xF0, | |
| 0xAD, | |
| 0xD4, | |
| 0xA2, | |
| 0xAF, | |
| 0x9C, | |
| 0xA4, | |
| 0x72, | |
| 0xC0, | |
| 0xB7, | |
| 0xFD, | |
| 0x93, | |
| 0x26, | |
| 0x36, | |
| 0x3F, | |
| 0xF7, | |
| 0xCC, | |
| 0x34, | |
| 0xA5, | |
| 0xE5, | |
| 0xF1, | |
| 0x71, | |
| 0xD8, | |
| 0x31, | |
| 0x15, | |
| 0x04, | |
| 0xC7, | |
| 0x23, | |
| 0xC3, | |
| 0x18, | |
| 0x96, | |
| 0x05, | |
| 0x9A, | |
| 0x07, | |
| 0x12, | |
| 0x80, | |
| 0xE2, | |
| 0xEB, | |
| 0x27, | |
| 0xB2, | |
| 0x75, | |
| 0x09, | |
| 0x83, | |
| 0x2C, | |
| 0x1A, | |
| 0x1B, | |
| 0x6E, | |
| 0x5A, | |
| 0xA0, | |
| 0x52, | |
| 0x3B, | |
| 0xD6, | |
| 0xB3, | |
| 0x29, | |
| 0xE3, | |
| 0x2F, | |
| 0x84, | |
| 0x53, | |
| 0xD1, | |
| 0x00, | |
| 0xED, | |
| 0x20, | |
| 0xFC, | |
| 0xB1, | |
| 0x5B, | |
| 0x6A, | |
| 0xCB, | |
| 0xBE, | |
| 0x39, | |
| 0x4A, | |
| 0x4C, | |
| 0x58, | |
| 0xCF, | |
| 0xD0, | |
| 0xEF, | |
| 0xAA, | |
| 0xFB, | |
| 0x43, | |
| 0x4D, | |
| 0x33, | |
| 0x85, | |
| 0x45, | |
| 0xF9, | |
| 0x02, | |
| 0x7F, | |
| 0x50, | |
| 0x3C, | |
| 0x9F, | |
| 0xA8, | |
| 0x51, | |
| 0xA3, | |
| 0x40, | |
| 0x8F, | |
| 0x92, | |
| 0x9D, | |
| 0x38, | |
| 0xF5, | |
| 0xBC, | |
| 0xB6, | |
| 0xDA, | |
| 0x21, | |
| 0x10, | |
| 0xFF, | |
| 0xF3, | |
| 0xD2, | |
| 0xCD, | |
| 0x0C, | |
| 0x13, | |
| 0xEC, | |
| 0x5F, | |
| 0x97, | |
| 0x44, | |
| 0x17, | |
| 0xC4, | |
| 0xA7, | |
| 0x7E, | |
| 0x3D, | |
| 0x64, | |
| 0x5D, | |
| 0x19, | |
| 0x73, | |
| 0x60, | |
| 0x81, | |
| 0x4F, | |
| 0xDC, | |
| 0x22, | |
| 0x2A, | |
| 0x90, | |
| 0x88, | |
| 0x46, | |
| 0xEE, | |
| 0xB8, | |
| 0x14, | |
| 0xDE, | |
| 0x5E, | |
| 0x0B, | |
| 0xDB, | |
| 0xE0, | |
| 0x32, | |
| 0x3A, | |
| 0x0A, | |
| 0x49, | |
| 0x06, | |
| 0x24, | |
| 0x5C, | |
| 0xC2, | |
| 0xD3, | |
| 0xAC, | |
| 0x62, | |
| 0x91, | |
| 0x95, | |
| 0xE4, | |
| 0x79, | |
| 0xE7, | |
| 0xC8, | |
| 0x37, | |
| 0x6D, | |
| 0x8D, | |
| 0xD5, | |
| 0x4E, | |
| 0xA9, | |
| 0x6C, | |
| 0x56, | |
| 0xF4, | |
| 0xEA, | |
| 0x65, | |
| 0x7A, | |
| 0xAE, | |
| 0x08, | |
| 0xBA, | |
| 0x78, | |
| 0x25, | |
| 0x2E, | |
| 0x1C, | |
| 0xA6, | |
| 0xB4, | |
| 0xC6, | |
| 0xE8, | |
| 0xDD, | |
| 0x74, | |
| 0x1F, | |
| 0x4B, | |
| 0xBD, | |
| 0x8B, | |
| 0x8A, | |
| 0x70, | |
| 0x3E, | |
| 0xB5, | |
| 0x66, | |
| 0x48, | |
| 0x03, | |
| 0xF6, | |
| 0x0E, | |
| 0x61, | |
| 0x35, | |
| 0x57, | |
| 0xB9, | |
| 0x86, | |
| 0xC1, | |
| 0x1D, | |
| 0x9E, | |
| 0xE1, | |
| 0xF8, | |
| 0x98, | |
| 0x11, | |
| 0x69, | |
| 0xD9, | |
| 0x8E, | |
| 0x94, | |
| 0x9B, | |
| 0x1E, | |
| 0x87, | |
| 0xE9, | |
| 0xCE, | |
| 0x55, | |
| 0x28, | |
| 0xDF, | |
| 0x8C, | |
| 0xA1, | |
| 0x89, | |
| 0x0D, | |
| 0xBF, | |
| 0xE6, | |
| 0x42, | |
| 0x68, | |
| 0x41, | |
| 0x99, | |
| 0x2D, | |
| 0x0F, | |
| 0xB0, | |
| 0x54, | |
| 0xBB, | |
| 0x16, | |
| ] | |
| def sub_bytes(state: list) -> list: | |
| """ | |
| The SubBytes step of AES. | |
| """ | |
| # Task 2: Implement the SubBytes step here | |
| return [SBOX[b] for b in state] | |
| def add_round_key(state: list, round_key: list) -> list: | |
| """ | |
| The AddRoundKey step of AES. | |
| """ | |
| # Task 3: Implement the AddRoundKey step here | |
| return [s ^ r for s, r in zip(state, round_key)] | |
| def shift_rows(state: list) -> list: | |
| """ | |
| The ShiftRows step of AES. | |
| """ | |
| return [ | |
| state[0], | |
| state[5], | |
| state[10], | |
| state[15], | |
| state[4], | |
| state[9], | |
| state[14], | |
| state[3], | |
| state[8], | |
| state[13], | |
| state[2], | |
| state[7], | |
| state[12], | |
| state[1], | |
| state[6], | |
| state[11], | |
| ] | |
| def xtime(a: int) -> int: | |
| return (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1) | |
| def mix_columns(state: list) -> list: | |
| """ | |
| The MixColumns step of AES. | |
| """ | |
| res = [0] * 16 | |
| for i in range(4): | |
| c = state[i * 4 : i * 4 + 4] | |
| res[i * 4 + 0] = xtime(c[0]) ^ (xtime(c[1]) ^ c[1]) ^ c[2] ^ c[3] | |
| res[i * 4 + 1] = c[0] ^ xtime(c[1]) ^ (xtime(c[2]) ^ c[2]) ^ c[3] | |
| res[i * 4 + 2] = c[0] ^ c[1] ^ xtime(c[2]) ^ (xtime(c[3]) ^ c[3]) | |
| res[i * 4 + 3] = (xtime(c[0]) ^ c[0]) ^ c[1] ^ c[2] ^ xtime(c[3]) | |
| return res | |
| def expand_key(key: bytes) -> list: | |
| """ | |
| AES-128 Key Expansion. | |
| """ | |
| Rcon = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36] | |
| w = [list(key[i : i + 4]) for i in range(0, 16, 4)] | |
| for i in range(4, 44): | |
| temp = list(w[i - 1]) | |
| if i % 4 == 0: | |
| temp = [SBOX[b] for b in temp[1:] + temp[:1]] | |
| temp[0] ^= Rcon[i // 4] | |
| w.append([w[i - 4][j] ^ temp[j] for j in range(4)]) | |
| return [sum(w[i : i + 4], []) for i in range(0, 44, 4)] | |
| def aes_encrypt(plaintext: bytes, key: bytes): | |
| """ | |
| An AES-128 encryption loop. | |
| """ | |
| round_keys = expand_key(key) | |
| state = list(plaintext) | |
| # Initial Round | |
| state = add_round_key(state, round_keys[0]) | |
| # Rounds 1 to 9 | |
| for i in range(1, 10): | |
| state = sub_bytes(state) | |
| state = shift_rows(state) | |
| state = mix_columns(state) | |
| state = add_round_key(state, round_keys[i]) | |
| # Final Round (No MixColumns) | |
| state = sub_bytes(state) | |
| state = shift_rows(state) | |
| state = add_round_key(state, round_keys[10]) | |
| return bytes(state) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from dataclasses import dataclass | |
| from hashlib import sha1 | |
| from typing import Tuple, Union | |
| from mod import Mod | |
| @dataclass | |
| class Point(object): | |
| """A point on an elliptic curve.""" | |
| x: Mod | |
| y: Mod | |
| @dataclass | |
| class Curve(object): | |
| """An elliptic curve.""" | |
| p: int | |
| a: Mod | |
| b: Mod | |
| g: Point | |
| n: int | |
| def __init__(self, p: int, a: int, b: int, gx: int, gy: int, n: int): | |
| self.p = p | |
| self.a = Mod(a, p) | |
| self.b = Mod(b, p) | |
| self.g = Point(Mod(gx, p), Mod(gy, p)) | |
| self.n = n | |
| def add(self, p: Point, q: Point): | |
| """ | |
| Add two points on an elliptic curve (If the points are equal, the result is erroneous). | |
| See <https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Point_addition>. | |
| """ | |
| λ = (q.y - p.y) / (q.x - p.x) | |
| x = λ**2 - p.x - q.x | |
| y = λ * (p.x - x) - p.y | |
| return Point(x, y) | |
| def dbl(self, p: Point): | |
| """ | |
| Double a point on an elliptic curve. | |
| See <https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Point_doubling>. | |
| """ | |
| λ = (3 * p.x**2 + self.a) / (2 * p.y) | |
| x = λ**2 - p.x - p.x | |
| y = λ * (p.x - x) - p.y | |
| return Point(x, y) | |
| curve_secp256r1 = Curve( | |
| 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF, | |
| 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC, | |
| 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B, | |
| 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296, | |
| 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5, | |
| 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551, | |
| ) | |
| def scalarmult(point: Point, scalar: Union[int, Mod], curve: Curve) -> Point: | |
| """ | |
| Perform scalar multiplication of the `point` with the `scalar` on the elliptic curve given by `curve`. | |
| See <https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication> for some methods. | |
| Returns `[scalar]point`. | |
| """ | |
| r0 = point | |
| r1 = curve.dbl(point) | |
| s = int(scalar) | |
| for i in range(s.bit_length() - 2, -1, -1): | |
| if s & (1 << i) == 0: | |
| r1 = curve.add(r0, r1) | |
| r0 = curve.dbl(r0) | |
| else: | |
| r0 = curve.add(r0, r1) | |
| r1 = curve.dbl(r1) | |
| return r0 | |
| def keygen(curve: Curve) -> Tuple[Mod, Point]: | |
| """ | |
| Generate an ECC keypair on the `curve`. | |
| Returns a tuple of `private key, public key`. | |
| """ | |
| private = Mod.random(curve.n) | |
| public = scalarmult(curve.g, int(private), curve) | |
| return private, public | |
| def sign(message: bytes, private: Mod, curve: Curve) -> Tuple[Mod, Mod]: | |
| """ | |
| Sign the `message` using the `private` key on the `curve`. | |
| Returns the signature tuple `r, s`. | |
| https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm | |
| """ | |
| # h = SHA1(message) and then the ECDSA trimming. | |
| h = int(sha1(message).hexdigest(), 16) >> ( | |
| 0 if 160 <= curve.n.bit_length() else 160 - curve.n.bit_length() | |
| ) | |
| # k = random integer from ℤₙ, the Mod.random function should be considered constant-time. | |
| k = Mod.random(curve.n) | |
| # r = ([k]G)_x mod n | |
| r = Mod(int(scalarmult(curve.g, int(k), curve).x), curve.n) | |
| # s = k^-1 * (H(message) + r * x) mod n | |
| s = k ** (-1) * (h + r * private) | |
| return r, s | |
| def verify(signature: Tuple[Mod, Mod], message: bytes, public: Point, curve: Curve) -> bool: | |
| """ | |
| Verify the `signature` on the `message` using the `public` key on the `curve`. | |
| Note, this is missing some checks. | |
| https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm | |
| """ | |
| # h = SHA1(message) and then the ECDSA trimming. | |
| h = int(sha1(message).hexdigest(), 16) >> ( | |
| 0 if 160 <= curve.n.bit_length() else 160 - curve.n.bit_length() | |
| ) | |
| r, s = signature | |
| if r == 0 or s == 0: | |
| raise ValueError("Malformed signature") | |
| sinv = s ** (-1) | |
| u1 = h * sinv | |
| u2 = r * sinv | |
| u1G = scalarmult(curve.g, u1, curve) | |
| u2P = scalarmult(public, u2, curve) | |
| res = curve.add(u1G, u2P) | |
| return int(r) == int(res.x) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import secrets | |
| from functools import wraps | |
| def check(func): | |
| @wraps(func) | |
| def method(self, other): | |
| if type(other) is type(self): | |
| if self.n != other.n: | |
| raise ValueError | |
| elif isinstance(other, int): | |
| other = self.__class__(other, self.n) | |
| else: | |
| raise ValueError | |
| return func(self, other) | |
| return method | |
| def extgcd(a, b): | |
| """Extended Euclid's greatest common denominator algorithm.""" | |
| if abs(b) > abs(a): | |
| (x, y, d) = extgcd(b, a) | |
| return y, x, d | |
| if abs(b) == 0: | |
| return 1, 0, a | |
| x1, x2, y1, y2 = 0, 1, 1, 0 | |
| while abs(b) > 0: | |
| q, r = divmod(a, b) | |
| x = x2 - q * x1 | |
| y = y2 - q * y1 | |
| a, b, x2, x1, y2, y1 = b, r, x1, x, y1, y | |
| return x2, y2, a | |
| class Mod(object): | |
| """ | |
| An element x of ℤₙ. | |
| Construct like this: | |
| Mod(5, 7) | |
| Works like this: | |
| Mod(5, 7) + Mod(3, 7) = Mod(1, 7) | |
| Then, get the integer out like this: | |
| int(Mod(3, 7)) = 3 | |
| """ | |
| def __init__(self, x: int, n: int): | |
| self.x: int = x % n | |
| self.n: int = n | |
| @check | |
| def __add__(self, other): | |
| return Mod((self.x + other.x) % self.n, self.n) | |
| @check | |
| def __radd__(self, other): | |
| return self + other | |
| @check | |
| def __sub__(self, other): | |
| return Mod((self.x - other.x) % self.n, self.n) | |
| @check | |
| def __rsub__(self, other): | |
| return -self + other | |
| def __neg__(self): | |
| return Mod(self.n - self.x, self.n) | |
| def inverse_gcd(self): | |
| x, y, d = extgcd(self.x, self.n) | |
| return Mod(x, self.n) | |
| def inverse_pow(self): | |
| return self ** (self.n - 2) | |
| def inverse(self): | |
| return self.inverse_gcd() | |
| def __invert__(self): | |
| return self.inverse() | |
| @check | |
| def __mul__(self, other): | |
| return Mod((self.x * other.x) % self.n, self.n) | |
| @check | |
| def __rmul__(self, other): | |
| return self * other | |
| @check | |
| def __truediv__(self, other): | |
| return self * ~other | |
| @check | |
| def __rtruediv__(self, other): | |
| return ~self * other | |
| @check | |
| def __floordiv__(self, other): | |
| return self * ~other | |
| @check | |
| def __rfloordiv__(self, other): | |
| return ~self * other | |
| @check | |
| def __div__(self, other): | |
| return self.__floordiv__(other) | |
| @check | |
| def __rdiv__(self, other): | |
| return self.__rfloordiv__(other) | |
| @check | |
| def __divmod__(self, divisor): | |
| q, r = divmod(self.x, divisor.x) | |
| return Mod(q, self.n), Mod(r, self.n) | |
| def __bytes__(self): | |
| return self.x.to_bytes((self.n.bit_length() + 7) // 8, byteorder="big") | |
| @staticmethod | |
| def random(n: int): | |
| return Mod(secrets.randbelow(n), n) | |
| def __int__(self): | |
| return self.x | |
| def __index__(self): | |
| return self.x | |
| def __eq__(self, other): | |
| if type(other) is int: | |
| return self.x == (other % self.n) | |
| if type(other) is not Mod: | |
| return False | |
| return self.x == other.x and self.n == other.n | |
| def __ne__(self, other): | |
| return not self == other | |
| def __repr__(self): | |
| return str(self.x) | |
| def __pow__(self, n): | |
| if type(n) is not int: | |
| raise TypeError | |
| if n == 0: | |
| return Mod(1, self.n) | |
| if n < 0: | |
| return self.inverse() ** (-n) | |
| if n == 1: | |
| return Mod(self.x, self.n) | |
| q = self | |
| r = self if n & 1 else Mod(1, self.n) | |
| i = 2 | |
| while i <= n: | |
| q = q * q | |
| if n & i == i: | |
| r = q * r | |
| i = i << 1 | |
| return r |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| jupyter | |
| notebook | |
| jupytext |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import secrets | |
| from mod import Mod | |
| def square_and_multiply(base: int, exponent: int, modulus: int) -> int: | |
| """ | |
| Computes (base**exponent) % modulus using square-and-multiply. | |
| """ | |
| # Task 4: Implement square and multiply | |
| res = Mod(1, modulus) | |
| bits = bin(exponent)[2:] | |
| for bit in bits: | |
| res = res**2 | |
| if bit == "1": | |
| res = res * base | |
| return int(res) | |
| def pkcs1_v15_pad(message: bytes, modulus_len: int) -> int: | |
| """ | |
| A PKCS#1 v1.5 padding scheme (for encryption). | |
| Structure: 0x00 || 0x02 || PS || 0x00 || Message | |
| https://www.rfc-editor.org/rfc/rfc8017#section-7.2.1 | |
| Warning: This is not a good padding scheme! It is vulnerable | |
| to Bleichenbacher's attack (see lecture 5 of Real World Crypto Engineering). | |
| """ | |
| if len(message) > modulus_len - 11: | |
| raise ValueError("Message too long for modulus") | |
| # Padding string (non-zero random bytes) | |
| ps_len = modulus_len - len(message) - 3 | |
| ps = b"" | |
| while len(ps) < ps_len: | |
| new_byte = secrets.token_bytes(1) | |
| if new_byte != b"\x00": | |
| ps += new_byte | |
| padded = b"\x00\x02" + ps + b"\x00" + message | |
| return int.from_bytes(padded, "big") | |
| def pkcs1_v15_unpad(padded_int: int, modulus_len: int) -> bytes: | |
| """ | |
| Removes the PKCS#1 v1.5 padding (for decryption). | |
| https://www.rfc-editor.org/rfc/rfc8017#section-7.2.2 | |
| Warning: This is not a good padding scheme! It is vulnerable | |
| to Bleichenbacher's attack (see lecture 5 of Real World Crypto Engineering). | |
| """ | |
| padded = padded_int.to_bytes(modulus_len, "big") | |
| if padded[0:2] != b"\x00\x02": | |
| raise ValueError("Invalid padding prefix") | |
| # Find the separator | |
| sep_idx = padded.find(b"\x00", 2) | |
| if sep_idx == -1: | |
| raise ValueError("No separator found") | |
| return padded[sep_idx + 1 :] | |
| def rsa_encrypt(m: bytes, e: int, n: int) -> int: | |
| """ | |
| RSA Encryption with padding. | |
| """ | |
| mod_len = (n.bit_length() + 7) // 8 | |
| padded_m = pkcs1_v15_pad(m, mod_len) | |
| return square_and_multiply(padded_m, e, n) | |
| def rsa_decrypt(c: int, d: int, n: int) -> bytes: | |
| """ | |
| RSA Decryption and unpadding. | |
| """ | |
| mod_len = (n.bit_length() + 7) // 8 | |
| padded_m = square_and_multiply(c, d, n) | |
| return pkcs1_v15_unpad(padded_m, mod_len) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment