{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Break AES using DPA with correlations\n", "\n", "You need:\n", "* `plaintext.txt`: all PT blocks, (one block per line, in hex, bytes separated by spaces)\n", "* `ciphertext.txt`: all CT blocks, (one block per line, in hex, bytes separated by spaces)\n", "* `traceLength.txt`: how many samples per trace (one decimal number)\n", "* `traces.bin`: raw measured traces, one byte per sample (uint8), all traces together continuously\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "GEwwR12Gupsi" }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "8fW8nPQ5uyEO" }, "outputs": [], "source": [ "# AES SBOX\n", "sbox = np.array([\n", " 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,\n", " 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,\n", " 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,\n", " 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,\n", " 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,\n", " 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,\n", " 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,\n", " 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,\n", " 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,\n", " 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,\n", " 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,\n", " 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,\n", " 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,\n", " 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,\n", " 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,\n", " 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16\n", " ], dtype='uint8')\n", "\n", "# Hamming weight lookup table\n", "hw_table = []\n", "for i in range(256):\n", " s = '{0:08b}'.format(i)\n", " hw_table.append(s.count('1'))\n", "hw_table = np.array(hw_table, 'uint8')\n", "\n", "# Correlation of two matrices\n", "def correlate(x, y):\n", " \"\"\"\n", " Correlate all columns from matrix x of shape (a,b)\n", " with all columns from matrix y of shape (a,c),\n", " creating correlation matrix C of shape (b,c).\n", " \n", " Originally matlab script by Jiri Bucek in NI-HWB.\n", " \"\"\"\n", " x = x - np.average(x, 0) # remove vertical averages\n", " y = y - np.average(y, 0) # remove vertical averages\n", " C = x.T @ y # (n-1) Cov(x,y)\n", " C = C / (np.sum(x**2, 0)**(1/2))[:,np.newaxis] # divide by (n-1) Var(x)\n", " C = C / (np.sum(y**2, 0)**(1/2)) # divide by (n-1) Var(y)\n", " return C\n", "\n", "# Load PT of CT from file\n", "def load_text(file_name):\n", " \"\"\"\n", " Load any text PT/CT from file containing hex strings with bytes \n", " separated by spaces, one block per line\n", " Output is a matrix of bytes (np.array)\n", " \"\"\"\n", " txt_str = open(file_name).readlines()\n", " del txt_str[-1] #discard last empty line\n", " #split each line into bytes and convert from hex\n", " txt_bytes_list = list(\n", " map(lambda line: \n", " list(\n", " map(lambda s: int(s, 16),\n", " line.rstrip().split(\" \"))\n", " ),\n", " txt_str)\n", " )\n", " return np.array(txt_bytes_list, 'uint8')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "--PH16eNuz_H" }, "outputs": [], "source": [ "# read plaintext inputs\n", "inputs = load_text(\"plaintext.txt\")\n", "\n", "# read length of one complete trace (number of samples per trace)\n", "with open(\"traceLength.txt\", \"r\") as fin:\n", " trace_length = int(fin.readline())\n", "\n", "# trim each trace - select interesting part\n", "start = 0\n", "len = trace_length # CHANGE to the length of the first round; \n", "\n", "# read traces from binary file\n", "traces = np.fromfile(\"traces.bin\", dtype='uint8') # read as linear array\n", "traces = np.reshape(traces, (traces.size // trace_length, trace_length)) # reshape into matrix\n", "traces = traces[:, start:len] # select only the interesting part of each trace" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "ZVJ_Tk55u1wu" }, "outputs": [], "source": [ "print(inputs.shape) # dimensions of inputs\n", "print(trace_length)\n", "print(traces.shape) # dimensions of matrix of traces" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "6hzUcHiWxyH0" }, "outputs": [], "source": [ "# If you feel brave enough -- interactive plots\n", "#!pip install ipympl\n", "#from google.colab import output\n", "#output.enable_custom_widget_manager()\n", "#%matplotlib widget" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "wDAUVmNOu3BP" }, "outputs": [], "source": [ "# Plot one trace\n", "fig = plt.figure()\n", "plt.plot(traces[0])\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "id": "w6boaqAQvF1G" }, "source": [ "## **Attack the first key byte**\n", "![Intermediate value](dpa-aes-v.png)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "WaKiOUmbvbQR" }, "outputs": [], "source": [ "# Generate key hypotheses (all possible byte values)\n", "keys = np.arange(start=0, stop=256, step=1, dtype='uint8')\n", "# Select the first byte of each input block\n", "inp = inputs[:, 0]\n", "# XOR each data byte with each key\n", "xmat = inp[:, np.newaxis] ^ keys" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Examine the inputs matrix. Does it contain the data from plaintext.txt?\n", "print(inputs)\n", "# What is the shape of all the operands from the previous cell?\n", "print(inputs.shape)\n", "print(inp.shape)\n", "print(inp[:, np.newaxis].shape)\n", "print(keys.shape)\n", "print(xmat.shape)\n", "# Do you understand the values after the XOR operation? What AES operation do they represent?\n", "print(xmat)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "VrBZd18VwBOH" }, "outputs": [], "source": [ "# Substitute with SBOX all XORed values -- matrix of intermediate values\n", "smat = sbox[?]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "4GfR9BU-wT4G" }, "outputs": [], "source": [ "# Compute Hamming Weights -- the matrix of hypothetical power consumption\n", "hmat = ?[?]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "J8TTPk-WwjQH" }, "outputs": [], "source": [ "# Compute the correlation matrix -- correlate the hypotheses with measured traces\n", "print(hmat.shape)\n", "print(traces.shape)\n", "corr = correlate(?, ?)\n", "# What is the shape and contents of the correlation matrix?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "iOqbuNAKxCvP" }, "outputs": [], "source": [ "# Find the absolute maximum correlation\n", "acorr = abs(?)\n", "max_acorr = ?.max()\n", "(k, j) = np.where(acorr == ?) # find idices of maximum\n", "print(\"key: %d time: %d\" % (k[0], j[0]))\n", "print(\"key: %1c, %02x\" % (k[0], k[0]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot the correlation traces for the right key byte guess and one wrong key byte guess\n", "# Do you see the correlation peaks?\n", "fig = plt.figure()\n", "plt.plot(?)\n", "plt.plot(?)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "id": "Z62RVYJYzncZ" }, "source": [ "## **Break all key bytes!**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "T7HhwO-ezpoQ" }, "outputs": [], "source": [ "keys = np.array(range(0, 256))\n", "kk = np.zeros(16, dtype='uint8')\n", "for i in range(0, 16):\n", " inp = inputs[:, ?]\n", " ????\n", " kk[i] = k\n", " print(\"%1c, %02x @ %d\" % (k[0], k[0], j[0]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **Verify the key on a PT, CT pair!**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "key_bytes = bytes(kk)\n", "outputs = ?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# !pip install pycryptodome\n", "from Crypto.Cipher import AES\n", "cipher = AES.new(key_bytes, AES.MODE_ECB)\n", "??" ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "dpa_student.ipynb", "provenance": [] }, "kernelspec": { "display_name": "Python 3", "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.9.1" } }, "nbformat": 4, "nbformat_minor": 0 }