As of August 2020 the site you are on (wiki.newae.com) is deprecated, and content is now at rtfm.newae.com.

Difference between revisions of "Tutorial B8 Profiling Attacks (Manual Template Attack)"

From ChipWhisperer Wiki
Jump to: navigation, search
(Added section on how to using python scripts)
(Creating the Template: Added section on sorting traces)
Line 49: Line 49:
  
 
== Sorting the Traces ==
 
== Sorting the Traces ==
 +
With the data in-hand, our next task is to group the data according to our model. We're attacking an intermediate result in the AES algorithm where
 +
 +
<pre>
 +
intermediate = sbox[plaintext ^ key]
 +
</pre>
 +
 +
In our attack, we're going to try to look at a trace and decide what the Hamming weight of this intermediate result is. To set up the templates, we need to sort our template traces into 9 groups. The first group will be all of the traces that have an intermediate Hamming weight of 0. The next group has a Hamming weight of 1, etc..., and the last group has a Hamming weight of 8.
 +
 +
To find which group a trace belongs in, we can calculate the intermediate result from the plaintext and key. Some Python code to do this is:
 +
 +
<pre>
 +
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)
 +
 +
intermediate = sbox[tempPText[0][0] ^ tempKey[0][0]]
 +
</pre>
 +
This will calculate the intermediate value for trace 0, looking only at subkey 0. This calculation can be repeated using list comprehension:
 +
<pre>
 +
tempSbox = [sbox[tempPText[i][0] ^ tempKey[i][0]] for i in range(len(tempPText))]
 +
</pre>
 +
 +
Then, we need to get the Hamming weight of all of these intermediate values. We can do this using the same lookup table as the previous tutorial:
 +
 +
<pre>
 +
hw = [bin(x).count("1") for x in range(256)]
 +
tempHW = [hw[s] for s in tempSbox]
 +
</pre>
 +
 +
Confirm that these look correct by printing them. (Would I ever steer you wrong?)
 +
 +
With these Hamming weights, we can look at every trace and decide which group it belongs in. Let's make a list of lists of traces:
 +
<pre>
 +
tempTracesHW = [[] for _ in range(9)]
 +
</pre>
 +
 +
Then, we can loop through our list of traces and append each trace to the right category:
 +
<pre>
 +
for i in range(len(tempTraces)):
 +
    HW = tempHW[i]
 +
    tempTracesHW[HW].append(tempTraces[i])
 +
</pre>
 +
 +
Now, <code>tempTracesHW[y]</code> is a list of all of the traces with an intermediate Hamming weight of <code>y</code>. As our last step, let's turn this into a NumPy array to make the math easier in the rest of the tutorial:
 +
<pre>
 +
tempTracesHW = np.array(tempTracesHW)
 +
</pre>
 +
 +
 
== Points of Interest ==
 
== Points of Interest ==
 
== Covariance Matrices ==
 
== Covariance Matrices ==

Revision as of 12:57, 25 May 2016

This tutorial is a more hands-on version of the previous tutorial. Rather than getting the ChipWhisperer Analyzer software to generate the points of interest and the template distributions, this tutorial will work directly with the recorded trace data in Python.

It is highly recommended that you read the theory page on Template Attacks before attempting this tutorial. There is some relatively complex processing involved, and it may be helpful to get a mathematical view on the steps before attempting to program them.

Additionally, this tutorial uses some terminology from previous tutorials, such as Hamming weight and substitution box. If you don't know what these are, Tutorial B6 Breaking AES (Manual CPA Attack) might be an easier starting point.

Scripting

All of the work in this tutorial will be done using Python scripts. All you need for this tutorial is a text editor and a command terminal that can run Python. In my example, I am editing a file called manualTemplate.py in a text editor and using the command

python manualTemplate.py

to execute the script. You can work with a full IDE if you prefer.

Capturing the Traces

As in the previous tutorial, this tutorial requires two sets of traces. The first set is a large number of traces (1000+) with random keys and plaintexts, assumed to come from your personal copy of the device. The second is a smaller number of traces (~50) with a fixed key and random plaintexts, assumed to come from the sensitive device that we're attacking. The goal of this tutorial is to recover the fixed key from the smaller set of traces.

The data collected from the previous tutorial will be fine for these steps. These examples will work with 2000 random-key traces and 50 fixed-key traces. If you don't have these datasets, follow the steps in Tutorial B7 Profiling Attacks (with HW Assumption) to record these traces.

Note that this tutorial will explain how to attack a single byte of the secret AES key. It would be easy to extend this to the full key by running the code 16 times. The smaller attack is used to make some of the code easier to grasp and debug.

Creating the Template

This section describes how to generate a template from the random-key traces. Our template will attempt to recognize the Hamming weight of the AES substitution box output. This choice of attack point limits our template - we will not be able to find the secret key in one attack trace - but it allows us to use a smaller amount of preprocessing. A more robust template attack (for instance, creating a template for each of the 256 possible key bytes) would require at least 10 times more data.

Loading the Traces

The traces recorded from the ChipWhisperer Capture program are saved as NumPy arrays. These files can be loaded using the np.load() function. We're interested in the traces, plaintext, and random keys used in our template captures, so we can load this data with the code:

import numpy as np
tempTraces = np.load(r'C:\chipwhisperer\software\temp_attack\rand_key_data\traces\2016.05.24-12.53.15_traces.npy')
tempPText  = np.load(r'C:\chipwhisperer\software\temp_attack\rand_key_data\traces\2016.05.24-12.53.15_textin.npy')
tempKey    = np.load(r'C:\chipwhisperer\software\temp_attack\rand_key_data\traces\2016.05.24-12.53.15_keylist.npy')

(Of course, fill in your own filenames.)

We can check this data to make sure it looks okay. Some useful checks might be:

import matplotlib.pyplot as plt
print tempPText
print len(tempPText)
plt.plot(tempTraces[0])
plt.show()

Get in the habit of checking your data with some basic print statements or plots - it's a good sanity check to make sure that your arrays are the size they should be! If everything looks okay, comment out these checks and move on.

Sorting the Traces

With the data in-hand, our next task is to group the data according to our model. We're attacking an intermediate result in the AES algorithm where

intermediate = sbox[plaintext ^ key]

In our attack, we're going to try to look at a trace and decide what the Hamming weight of this intermediate result is. To set up the templates, we need to sort our template traces into 9 groups. The first group will be all of the traces that have an intermediate Hamming weight of 0. The next group has a Hamming weight of 1, etc..., and the last group has a Hamming weight of 8.

To find which group a trace belongs in, we can calculate the intermediate result from the plaintext and key. Some Python code to do this is:

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) 

intermediate = sbox[tempPText[0][0] ^ tempKey[0][0]]

This will calculate the intermediate value for trace 0, looking only at subkey 0. This calculation can be repeated using list comprehension:

tempSbox = [sbox[tempPText[i][0] ^ tempKey[i][0]] for i in range(len(tempPText))]

Then, we need to get the Hamming weight of all of these intermediate values. We can do this using the same lookup table as the previous tutorial:

hw = [bin(x).count("1") for x in range(256)]
tempHW = [hw[s] for s in tempSbox]

Confirm that these look correct by printing them. (Would I ever steer you wrong?)

With these Hamming weights, we can look at every trace and decide which group it belongs in. Let's make a list of lists of traces:

tempTracesHW = [[] for _ in range(9)]

Then, we can loop through our list of traces and append each trace to the right category:

for i in range(len(tempTraces)):
    HW = tempHW[i]
    tempTracesHW[HW].append(tempTraces[i])

Now, tempTracesHW[y] is a list of all of the traces with an intermediate Hamming weight of y. As our last step, let's turn this into a NumPy array to make the math easier in the rest of the tutorial:

tempTracesHW = np.array(tempTracesHW)


Points of Interest

Covariance Matrices

Performing the Attack

Steps to crack the code (tm)

Loading the Traces

Using the Template

Gotchas

  • Too little data (0 or 1 trace)
  • Flukes + statistics