PA_DPA_3-AES_DPA_Attack

Supported setups:

SCOPES:

  • OPENADC

  • CWNANO

PLATFORMS:

  • CWLITEARM

  • CWLITEXMEGA - NOTE: this attack is less reliable on the CWLITEXMEGA than other targets

  • CWNANO

Before starting this tutorial, it’s recommended that you first complete the earlier PA_DPA tutorials since these will familiarize you with the concept of Differental Power Analysis. With that out of the way, let’s look at how this attack works.

DPA Attack Theory

As we explored in the earlier DPA tutorials, the Hamming Weight of the result of the SBox operation in AES has a measurable effect on the power consumed by the microcontroller. It turns out that just this effect (and not anything stronger, such as its linearity) is enough information to break an AES key. There’s a few different ways we could go about this, but for this tutorial, we’ll be looking at difference of means. With this technique, the goal is to separate the traces by a bit in the result of the SBox output (it doesn’t matter which one): if that bit is 1, its group of traces should, on average, have higher power consumption during the SBox operation than the other set.

Whether or not we get a large difference in the means between these two groups depends on whether they were properly sorted into these groups. If not, there should be, on average, little difference between the two and therefore a low difference of means. Recall the SBox operation:

title

title

The SBox output depends on the subkey, which we don’t know (and the plaintext, which we do). However, since there’s a large difference of means for the correct key and small ones for the rest of the possible subkeys, we have a method of checking whether a given subkey is correct. If we calculate the difference of means for each subkey, the correct one will have the largest difference of means.

Our plan looks as follows 1. Capture a bunch of power traces with varying plaintext 1. Group each trace by the value of their SBox output’s lowest bit for a given key guess 1. Calculate the difference of means 1. Repeat for each possible subkey 1. Select the largest difference of means -> this is the correct subkey 1. Repeat for each subkey in the key

At the end, we should get a correct AES key!

In [1]:

SCOPETYPE = 'OPENADC'
PLATFORM = 'CWLITEXMEGA'
CRYPTO_TARGET = 'AVRCRYPTOLIB'

In [2]:

%%bash -s "$PLATFORM" "$CRYPTO_TARGET"
cd ../hardware/victims/firmware/simpleserial-aes
make PLATFORM=$1 CRYPTO_TARGET=$2

Out [2]:

rm -f -- simpleserial-aes-CWLITEXMEGA.hex

rm -f -- simpleserial-aes-CWLITEXMEGA.eep

rm -f -- simpleserial-aes-CWLITEXMEGA.cof

rm -f -- simpleserial-aes-CWLITEXMEGA.elf

rm -f -- simpleserial-aes-CWLITEXMEGA.map

rm -f -- simpleserial-aes-CWLITEXMEGA.sym

rm -f -- simpleserial-aes-CWLITEXMEGA.lss

rm -f -- objdir/*.o

rm -f -- objdir/*.lst

rm -f -- simpleserial-aes.s simpleserial.s XMEGA_AES_driver.s uart.s usart_driver.s xmega_hal.s aes-independant.s aes_enc.s aes_keyschedule.s aes_sbox.s aes128_enc.s

rm -f -- simpleserial-aes.d simpleserial.d XMEGA_AES_driver.d uart.d usart_driver.d xmega_hal.d aes-independant.d aes_enc.d aes_keyschedule.d aes_sbox.d aes128_enc.d

rm -f -- simpleserial-aes.i simpleserial.i XMEGA_AES_driver.i uart.i usart_driver.i xmega_hal.i aes-independant.i aes_enc.i aes_keyschedule.i aes_sbox.i aes128_enc.i

.

-------- begin --------

avr-gcc (WinAVR 20100110) 4.3.3

Copyright (C) 2008 Free Software Foundation, Inc.

This is free software; see the source for copying conditions.  There is NO

warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.



.

Compiling C: simpleserial-aes.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/simpleserial-aes.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/simpleserial-aes.o.d simpleserial-aes.c -o objdir/simpleserial-aes.o

.

Compiling C: .././simpleserial/simpleserial.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/simpleserial.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/simpleserial.o.d .././simpleserial/simpleserial.c -o objdir/simpleserial.o

.

Compiling C: .././hal/xmega/XMEGA_AES_driver.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/XMEGA_AES_driver.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/XMEGA_AES_driver.o.d .././hal/xmega/XMEGA_AES_driver.c -o objdir/XMEGA_AES_driver.o

.

Compiling C: .././hal/xmega/uart.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/uart.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/uart.o.d .././hal/xmega/uart.c -o objdir/uart.o

.

Compiling C: .././hal/xmega/usart_driver.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/usart_driver.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/usart_driver.o.d .././hal/xmega/usart_driver.c -o objdir/usart_driver.o

.

Compiling C: .././hal/xmega/xmega_hal.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/xmega_hal.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/xmega_hal.o.d .././hal/xmega/xmega_hal.c -o objdir/xmega_hal.o

.

Compiling C: .././crypto/aes-independant.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/aes-independant.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/aes-independant.o.d .././crypto/aes-independant.c -o objdir/aes-independant.o

.

Compiling C: .././crypto/avrcryptolib//aes/aes_enc.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/aes_enc.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/aes_enc.o.d .././crypto/avrcryptolib//aes/aes_enc.c -o objdir/aes_enc.o

.

Compiling C: .././crypto/avrcryptolib//aes/aes_keyschedule.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/aes_keyschedule.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/aes_keyschedule.o.d .././crypto/avrcryptolib//aes/aes_keyschedule.c -o objdir/aes_keyschedule.o

.

Compiling C: .././crypto/avrcryptolib//aes/aes_sbox.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/aes_sbox.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/aes_sbox.o.d .././crypto/avrcryptolib//aes/aes_sbox.c -o objdir/aes_sbox.o

.

Compiling C: .././crypto/avrcryptolib//aes/aes128_enc.c

avr-gcc -c -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/aes128_enc.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/aes128_enc.o.d .././crypto/avrcryptolib//aes/aes128_enc.c -o objdir/aes128_enc.o

.

Assembling: .././crypto/avrcryptolib//gf256mul/gf256mul.S

avr-gcc -c -mmcu=atxmega128d3 -I. -x assembler-with-cpp -DF_CPU=7372800 -Wa,-gstabs,-adhlns=objdir/gf256mul.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul .././crypto/avrcryptolib//gf256mul/gf256mul.S -o objdir/gf256mul.o

.

Linking: simpleserial-aes-CWLITEXMEGA.elf

avr-gcc -mmcu=atxmega128d3 -I. -DNO_EXTRA_OPTS -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_1 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DAVRCRYPTOLIB -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/simpleserial-aes.o -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -I.././crypto/avrcryptolib//aes -I.././crypto/avrcryptolib//gf256mul -std=gnu99 -MMD -MP -MF .dep/simpleserial-aes-CWLITEXMEGA.elf.d objdir/simpleserial-aes.o objdir/simpleserial.o objdir/XMEGA_AES_driver.o objdir/uart.o objdir/usart_driver.o objdir/xmega_hal.o objdir/aes-independant.o objdir/aes_enc.o objdir/aes_keyschedule.o objdir/aes_sbox.o objdir/aes128_enc.o objdir/gf256mul.o --output simpleserial-aes-CWLITEXMEGA.elf -Wl,-Map=simpleserial-aes-CWLITEXMEGA.map,--cref   -lm

.

Creating load file for Flash: simpleserial-aes-CWLITEXMEGA.hex

avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature simpleserial-aes-CWLITEXMEGA.elf simpleserial-aes-CWLITEXMEGA.hex

.

Creating load file for EEPROM: simpleserial-aes-CWLITEXMEGA.eep

avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" 
    --change-section-lma .eeprom=0 --no-change-warnings -O ihex simpleserial-aes-CWLITEXMEGA.elf simpleserial-aes-CWLITEXMEGA.eep || exit 0

.

Creating Extended Listing: simpleserial-aes-CWLITEXMEGA.lss

avr-objdump -h -S -z simpleserial-aes-CWLITEXMEGA.elf > simpleserial-aes-CWLITEXMEGA.lss

.

Creating Symbol Table: simpleserial-aes-CWLITEXMEGA.sym

avr-nm -n simpleserial-aes-CWLITEXMEGA.elf > simpleserial-aes-CWLITEXMEGA.sym

Size after:

   text        data     bss     dec     hex filename

   3440          32     228    3700     e74 simpleserial-aes-CWLITEXMEGA.elf

+--------------------------------------------------------

+ Built for platform CW-Lite XMEGA

+--------------------------------------------------------

Capturing Power Traces

Capture and setup is similar to earlier tutorials. We’ll have to capture a fair number of traces (usually a few thousand here) since Difference of Means isn’t a super trace efficient method. As you’ll find during the CPA tutorials, CPA is much better in this regard - it can often break AES implementations such as these in under 50 traces.

You may also find that you need to modify gain settings and the number of traces you capture - this attack is much more sensitive to gain settings and noise than a CPA attack would be.

Setup

In [3]:

%run "Helper_Scripts/Setup_Generic.ipynb"

In [4]:

fw_path = "../hardware/victims/firmware/simpleserial-aes/simpleserial-aes-{}.hex".format(PLATFORM)

In [5]:

cw.program_target(scope, prog, fw_path)

Out [5]:

XMEGA Programming flash...
XMEGA Reading flash...
Verified flash OK, 3471 bytes

Capture

In [6]:

#Capture Traces
from tqdm import tnrange
import numpy as np
import time

ktp = cw.ktp.Basic()

traces = []
N = 2500  # Number of traces

if PLATFORM == "CWLITEARM" or PLATFORM == "CW308_STM32F3":
    scope.adc.samples = 4000
elif PLATFORM == "CWLITEXMEGA" or PLATFORM == "CW303":
    scope.gain.db = 34 #works best with this gain for some reason
    scope.adc.samples = 1700 - 170
    scope.adc.offset = 500 + 700 + 170
    N = 5000

print(scope)
for i in tnrange(N, desc='Capturing traces'):
    key, text = ktp.next()  # manual creation of a key, text pair can be substituted here

    trace = cw.capture_trace(scope, target, text, key)
    if trace is None:
        continue
    traces.append(trace)

#Convert traces to numpy arrays
trace_array = np.asarray([trace.wave for trace in traces])
textin_array = np.asarray([trace.textin for trace in traces])
known_keys = np.asarray([trace.key for trace in traces])  # for fixed key, these keys are all the same

Out [6]:

cwlite Device
gain =
    mode = high
    gain = 44
    db   = 33.859375
adc =
    state      = False
    basic_mode = rising_edge
    timeout    = 2
    offset     = 1370
    presamples = 0
    samples    = 1530
    decimate   = 1
    trig_count = 7704571
clock =
    adc_src       = clkgen_x4
    adc_phase     = 0
    adc_freq      = 29538459
    adc_rate      = 29538459.0
    adc_locked    = True
    freq_ctr      = 0
    freq_ctr_src  = extclk
    clkgen_src    = system
    extclk_freq   = 10000000
    clkgen_mul    = 2
    clkgen_div    = 26
    clkgen_freq   = 7384615.384615385
    clkgen_locked = True
trigger =
    triggers = tio4
    module   = basic
io =
    tio1       = serial_rx
    tio2       = serial_tx
    tio3       = high_z
    tio4       = high_z
    pdid       = high_z
    pdic       = high_z
    nrst       = high_z
    glitch_hp  = False
    glitch_lp  = False
    extclk_src = hs1
    hs2        = clkgen
    target_pwr = True
glitch =
    clk_src     = target
    width       = 10.15625
    width_fine  = 0
    offset      = 10.15625
    offset_fine = 0
    trigger_src = manual
    arm_timing  = after_scope
    ext_offset  = 0
    repeat      = 1
    output      = clock_xor
WARNING:root:Trigger not found in ADC data. No data reported!
c:usersusercodeterm3chipwhisperersoftwarechipwhisperer__init__.py:289: UserWarning: Timeout happened during capture
  warnings.warn("Timeout happened during capture")
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!
WARNING:root:Trigger not found in ADC data. No data reported!

Analysis

As we discussed above, our goal here is to find the biggest difference of means out of the possible subkey values we could have. First, we’ll get some values and functions that will be useful for our calculations. We’ll be using intermediate() later to get the output of the SBox from a plaintext and key input.

In [7]:

numtraces = np.shape(trace_array)[0] #total number of traces
numpoints = np.shape(trace_array)[1] #samples per trace

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 intermediate(pt, keyguess):
    return sbox[pt ^ keyguess]

Our first step here will be separating our traces into different groups based on the SBox’s output. As mentioned earlier, we’re separating based on the least significant bit, but really any bit would work (as a test, you can change this and see if the attack still works):

one_list = []
zero_list = []
for tnum in range(numtraces):
    if (intermediate(textin_array[tnum][subkey], kguess) & 1):
        one_list.append(trace_array[tnum])
    else:
        zero_list.append(trace_array[tnum])

Then calculate the difference of means:

one_avg = np.asarray(one_list).mean(axis=0)
zero_avg = np.asarray(zero_list).mean(axis=0)
mean_diffs[kguess] = np.max(abs(one_avg - zero_avg))

We’ll need to repeat this with each possible key guess and then pick the one with the highest difference of means:

guess = np.argsort(mean_diffs)[-1]
key_guess.append(guess)
print(hex(guess))
print(mean_diffs[guess])

Finally, altogether and attacking all of the subkeys:

In [8]:

from tqdm import tnrange
import numpy as np
mean_diffs = np.zeros(255)
key_guess = []
known_key = known_keys[0]
plots = []
for subkey in tnrange(0, 16, desc="Attacking Subkey"):
    for kguess in tnrange(255, desc="Keyguess", leave=False):
        one_list = []
        zero_list = []

        for tnum in range(numtraces):
            if (intermediate(textin_array[tnum][subkey], kguess) & 1): #LSB is 1
                one_list.append(trace_array[tnum])
            else:
                zero_list.append(trace_array[tnum])
        one_avg = np.asarray(one_list).mean(axis=0)
        zero_avg = np.asarray(zero_list).mean(axis=0)
        mean_diffs[kguess] = np.max(abs(one_avg - zero_avg))
        if kguess == known_key[subkey]:
            plots.append(abs(one_avg - zero_avg))
    guess = np.argsort(mean_diffs)[-1]
    key_guess.append(guess)
    print(hex(guess) + "(real = 0x{:02X})".format(known_key[subkey]))
    #mean_diffs.sort()
    print(mean_diffs[guess])
    print(mean_diffs[known_key[subkey]])

Out [8]:

0x2b(real = 0x2B)
0.015737342194881515
0.015737342194881515
0x7e(real = 0x7E)
0.016910595086521485
0.016910595086521485
0x15(real = 0x15)
0.016639102921090443
0.016639102921090443
0x16(real = 0x16)
0.01759248774259964
0.01759248774259964
0x28(real = 0x28)
0.017090791381838888
0.017090791381838888
0xae(real = 0xAE)
0.01660081764218313
0.01660081764218313
0xd2(real = 0xD2)
0.016924318518190817
0.016924318518190817

With that done, we should now have the correct key:

In [9]:

print(key_guess)
print(known_key)

Out [9]:

[43, 126, 21, 22, 40, 174, 210, 166, 171, 247, 21, 136, 9, 207, 79, 60]
[ 43 126  21  22  40 174 210 166 171 247  21 136   9 207  79  60]

We can also plot the difference of means for a few of the correct subkey bytes:

In [10]:

from bokeh.plotting import figure, show
from bokeh.io import output_notebook

output_notebook()
p = figure()
p.line(range(numpoints), plots[0], line_color='green')
p.line(range(numpoints), plots[1], line_color='red')
p.line(range(numpoints), plots[15], line_color='blue')
show(p)

Out [10]:

Loading BokehJS ...

Conclusion

Congratulations, you have (hopefully) broken AES using a DPA attack! As you might have discovered during this tutorial, there can be quite a few issues with the difference of means method for breaking AES keys:

  • It’s quite susceptible to noise

  • The attack can easily pick up other parts of the AES operation

  • The attack typically requires a lot of traces. These software AES implementations are pretty weak against power analysis, but they still required thousands of traces to break

  • Some targets (such as the XMega) require fine tuning settings to make the attack work

Nevertheless, using a difference of means attack can still be very useful. For example, a later tutorial, PA_Multi_1, uses a difference of means attack similar in concept to this one to break the signature of an AES256 bootloader.

Tests

In [11]:

assert (known_key == key_guess).all(), "Failed to break key.\nGot: {}\nExp: {}".format(key_guess, known_key)

In [ ]: