Tutorial: Recovering an AES key by Differential Fault Analysis attack

Supported setups:

SCOPES:

  • OPENADC

PLATFORMS:

  • CWLITEARM
  • CWLITEXMEGA

This tutorial will introduce you to the Differential Fault Analysis (DFA) attack. It will show you how to configure the target to perform AES encryptions, how to glitch the encryption operations and introduce errors in the computed ciphertext and finally how to process these glitched ciphertexts to extract the AES key. This can be used as how-to for attacking other targets as well.

Original author: Philippe Teuwen (@doegox)

License: CC-BY-SA

Improvements are welcome!

Thanks to Philippe Teuwen for contributing this tutorial, he was the winner of the ChipWhisperer 2018 Contest with this submission!

In [1]:

SCOPETYPE = 'OPENADC'
PLATFORM = 'CWLITEARM'
CRYPTO_TARGET = 'TINYAES128C'

Prerequisites

On giants’ shoulders

This tutorial is the first to deal with DFA, nevertheless it was not designed from scratch. It relies on multiple sources we strongly encourage your to read as well for a better understanding of the details.

  • The simpleserial-aes target firmware, which contains the software AES implementation used in other side-channel analysis tutorials such as Using CW Analyzer for CPA Attack. This is the implementation we will attack.
  • The glitching tutorials:
  • Introduction to Glitch Attacks, useful to understand the hardware implementation of the glitching module
  • Tutorial CW305-3 Clock Glitching (wiki) which also presented briefly the principles of glitching AES, but without exploiting the faults
  • The DFA attack itself: there is no DFA cryptanalysis code in the Chipwhisperer but we’ll re-use a Python library the author of this tutorial wrote for attacking white-box implementations. It’s called phoenixAES and it is available on Github and on PyPI.

Brief introduction to Differential Fault Analysis

We’ll only describe the general principle and the operational constraints.

The principle is to repeat the same AES operation over and over and to glitch its intermediate operations to get an output cryptographically incorrect. There are many DFA algorithms which, depending on the nature of the fault (single bit?, single byte?, how many faulted outputs can we collect?…), are able to recover the last round key of the AES with various computations that may be quite intensive.

This tutorial covers the recovering of the key of a simple AES-128 encryption.

We’ll use a quite simple DFA published initially by Dusart, Letourneux and Vivolo in 2002 which has nice properties. For a mathematical deep-dive of the DFA we’re using in this tutorial, you can read Differential Fault Analysis on White-box AES Implementations as we will use the exact same DFA library. Moreover, the blog post explains how to tackle DFA against AES decryption and how to attack more than one round, which is required to attack AES-192 or AES-256.

AES-128 is made of 10 rounds, the last one is missing the MixColumn operation, the only operation which brings diffusion, i.e. it’s an operation which makes a single byte of one round state affecting multiple bytes in the next round, 4 bytes to be exact.

aes\_operations.png

aes_operations.png

(source: http://www.iis.ee.ethz.ch/~kgf/acacia/fig/aes.png)

So, if we inject a fault which affects a single byte between the last two MixColumn operations, it will propagate and 4 of the 16 output bytes will be wrong. We don’t need to know precisely where we inject our faults, we can simply observe the output and look for a 4-byte fault with one of the 4 possible patterns. The attack is differential because we observe the difference between the correct output and the faulty outputs. We’ll save you the maths but with 2 such faults on the same column, there is a high probability to recover a quarter of the round key, so with 4*2 faults we can recover the entire round key. And because the AES keyschedule is invertible, we can compute it backwards and recover the first round key, which is by definition equal to the AES-128 key.

So, our operational constraints are quite simple: be able to run several times the same AES encryption, with the same key (doh!) and the same plaintext input and be able to collect the ciphertexts. Note that technically we don’t need to know the value of the plaintext, we only need it to be constant.

Installing dependencies

Firstly, let’s install phoenixAES in the current kernel environment:

In [2]:

import sys
!{sys.executable} -m pip install phoenixAES

Out [2]:

Requirement already satisfied: phoenixAES in c:usersuserappdatalocalprogramspythonpython37-32libsite-packages (0.0.2)

Target

Which target?

Let’s recap. This tutorial is specifically focusing on: * AES-128 encryption * the Chipwhisperer Lite ARM or XMEGA target * AVR Crypto Lib or TinyAES128C * clock glitching

Other targets and AES implementations should be equally working as well as power glitching. Obviously the glitching parameters will have to be adapted to the corresponding target, which is often not that straightforward.

Even if you run this tutorial on the same Chipwhisperer Lite target hardware, you might have to alter slightly the glitching parameters to be able to get working glitches. Glitching is so sensitive that running twice the exact same attack hardly produce the exact same results.

Building the target firmware

If you have the avr-gcc toolchain installed, you should be able to build the simpleserial-aes-CW303 firmware:

In [3]:

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

Out [3]:

rm -f -- simpleserial-aes-CWLITEARM.hex

rm -f -- simpleserial-aes-CWLITEARM.eep

rm -f -- simpleserial-aes-CWLITEARM.cof

rm -f -- simpleserial-aes-CWLITEARM.elf

rm -f -- simpleserial-aes-CWLITEARM.map

rm -f -- simpleserial-aes-CWLITEARM.sym

rm -f -- simpleserial-aes-CWLITEARM.lss

rm -f -- objdir/*.o

rm -f -- objdir/*.lst

rm -f -- simpleserial-aes.s simpleserial.s stm32f3_hal.s stm32f3_hal_lowlevel.s stm32f3_sysmem.s aes.s aes-independant.s

rm -f -- simpleserial-aes.d simpleserial.d stm32f3_hal.d stm32f3_hal_lowlevel.d stm32f3_sysmem.d aes.d aes-independant.d

rm -f -- simpleserial-aes.i simpleserial.i stm32f3_hal.i stm32f3_hal_lowlevel.i stm32f3_sysmem.i aes.i aes-independant.i

.

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

arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907]

Copyright (C) 2017 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

arm-none-eabi-gcc -c -mcpu=cortex-m4 -I. -DNO_EXTRA_OPTS -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fmessage-length=0 -ffunction-sections -gdwarf-2 -DSS_VER=SS_VER_1_1 -DSTM32F303xC -DSTM32F3 -DSTM32 -DDEBUG -DHAL_TYPE=HAL_stm32f3 -DPLATFORM=CWLITEARM -DTINYAES128C -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/stm32f3 -I.././hal/stm32f3/CMSIS -I.././hal/stm32f3/CMSIS/core -I.././hal/stm32f3/CMSIS/device -I.././hal/stm32f4/Legacy -I.././crypto/ -I.././crypto/tiny-AES128-C -std=gnu99 -MMD -MP -MF .dep/simpleserial-aes.o.d simpleserial-aes.c -o objdir/simpleserial-aes.o

.

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

arm-none-eabi-gcc -c -mcpu=cortex-m4 -I. -DNO_EXTRA_OPTS -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fmessage-length=0 -ffunction-sections -gdwarf-2 -DSS_VER=SS_VER_1_1 -DSTM32F303xC -DSTM32F3 -DSTM32 -DDEBUG -DHAL_TYPE=HAL_stm32f3 -DPLATFORM=CWLITEARM -DTINYAES128C -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/simpleserial.lst -I.././simpleserial/ -I.././hal -I.././hal/stm32f3 -I.././hal/stm32f3/CMSIS -I.././hal/stm32f3/CMSIS/core -I.././hal/stm32f3/CMSIS/device -I.././hal/stm32f4/Legacy -I.././crypto/ -I.././crypto/tiny-AES128-C -std=gnu99 -MMD -MP -MF .dep/simpleserial.o.d .././simpleserial/simpleserial.c -o objdir/simpleserial.o

.

Compiling C: .././hal/stm32f3/stm32f3_hal.c

arm-none-eabi-gcc -c -mcpu=cortex-m4 -I. -DNO_EXTRA_OPTS -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fmessage-length=0 -ffunction-sections -gdwarf-2 -DSS_VER=SS_VER_1_1 -DSTM32F303xC -DSTM32F3 -DSTM32 -DDEBUG -DHAL_TYPE=HAL_stm32f3 -DPLATFORM=CWLITEARM -DTINYAES128C -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/stm32f3_hal.lst -I.././simpleserial/ -I.././hal -I.././hal/stm32f3 -I.././hal/stm32f3/CMSIS -I.././hal/stm32f3/CMSIS/core -I.././hal/stm32f3/CMSIS/device -I.././hal/stm32f4/Legacy -I.././crypto/ -I.././crypto/tiny-AES128-C -std=gnu99 -MMD -MP -MF .dep/stm32f3_hal.o.d .././hal/stm32f3/stm32f3_hal.c -o objdir/stm32f3_hal.o

.

Compiling C: .././hal/stm32f3/stm32f3_hal_lowlevel.c

arm-none-eabi-gcc -c -mcpu=cortex-m4 -I. -DNO_EXTRA_OPTS -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fmessage-length=0 -ffunction-sections -gdwarf-2 -DSS_VER=SS_VER_1_1 -DSTM32F303xC -DSTM32F3 -DSTM32 -DDEBUG -DHAL_TYPE=HAL_stm32f3 -DPLATFORM=CWLITEARM -DTINYAES128C -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/stm32f3_hal_lowlevel.lst -I.././simpleserial/ -I.././hal -I.././hal/stm32f3 -I.././hal/stm32f3/CMSIS -I.././hal/stm32f3/CMSIS/core -I.././hal/stm32f3/CMSIS/device -I.././hal/stm32f4/Legacy -I.././crypto/ -I.././crypto/tiny-AES128-C -std=gnu99 -MMD -MP -MF .dep/stm32f3_hal_lowlevel.o.d .././hal/stm32f3/stm32f3_hal_lowlevel.c -o objdir/stm32f3_hal_lowlevel.o

.

Compiling C: .././hal/stm32f3/stm32f3_sysmem.c

arm-none-eabi-gcc -c -mcpu=cortex-m4 -I. -DNO_EXTRA_OPTS -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fmessage-length=0 -ffunction-sections -gdwarf-2 -DSS_VER=SS_VER_1_1 -DSTM32F303xC -DSTM32F3 -DSTM32 -DDEBUG -DHAL_TYPE=HAL_stm32f3 -DPLATFORM=CWLITEARM -DTINYAES128C -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/stm32f3_sysmem.lst -I.././simpleserial/ -I.././hal -I.././hal/stm32f3 -I.././hal/stm32f3/CMSIS -I.././hal/stm32f3/CMSIS/core -I.././hal/stm32f3/CMSIS/device -I.././hal/stm32f4/Legacy -I.././crypto/ -I.././crypto/tiny-AES128-C -std=gnu99 -MMD -MP -MF .dep/stm32f3_sysmem.o.d .././hal/stm32f3/stm32f3_sysmem.c -o objdir/stm32f3_sysmem.o

.

Compiling C: .././crypto/tiny-AES128-C/aes.c

arm-none-eabi-gcc -c -mcpu=cortex-m4 -I. -DNO_EXTRA_OPTS -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fmessage-length=0 -ffunction-sections -gdwarf-2 -DSS_VER=SS_VER_1_1 -DSTM32F303xC -DSTM32F3 -DSTM32 -DDEBUG -DHAL_TYPE=HAL_stm32f3 -DPLATFORM=CWLITEARM -DTINYAES128C -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/aes.lst -I.././simpleserial/ -I.././hal -I.././hal/stm32f3 -I.././hal/stm32f3/CMSIS -I.././hal/stm32f3/CMSIS/core -I.././hal/stm32f3/CMSIS/device -I.././hal/stm32f4/Legacy -I.././crypto/ -I.././crypto/tiny-AES128-C -std=gnu99 -MMD -MP -MF .dep/aes.o.d .././crypto/tiny-AES128-C/aes.c -o objdir/aes.o

.

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

arm-none-eabi-gcc -c -mcpu=cortex-m4 -I. -DNO_EXTRA_OPTS -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fmessage-length=0 -ffunction-sections -gdwarf-2 -DSS_VER=SS_VER_1_1 -DSTM32F303xC -DSTM32F3 -DSTM32 -DDEBUG -DHAL_TYPE=HAL_stm32f3 -DPLATFORM=CWLITEARM -DTINYAES128C -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/stm32f3 -I.././hal/stm32f3/CMSIS -I.././hal/stm32f3/CMSIS/core -I.././hal/stm32f3/CMSIS/device -I.././hal/stm32f4/Legacy -I.././crypto/ -I.././crypto/tiny-AES128-C -std=gnu99 -MMD -MP -MF .dep/aes-independant.o.d .././crypto/aes-independant.c -o objdir/aes-independant.o

.

Assembling: .././hal/stm32f3/stm32f3_startup.S

arm-none-eabi-gcc -c -mcpu=cortex-m4 -I. -x assembler-with-cpp -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fmessage-length=0 -ffunction-sections -DF_CPU=7372800 -Wa,-gstabs,-adhlns=objdir/stm32f3_startup.lst -I.././simpleserial/ -I.././hal -I.././hal/stm32f3 -I.././hal/stm32f3/CMSIS -I.././hal/stm32f3/CMSIS/core -I.././hal/stm32f3/CMSIS/device -I.././hal/stm32f4/Legacy -I.././crypto/ -I.././crypto/tiny-AES128-C .././hal/stm32f3/stm32f3_startup.S -o objdir/stm32f3_startup.o

.

Linking: simpleserial-aes-CWLITEARM.elf

arm-none-eabi-gcc -mcpu=cortex-m4 -I. -DNO_EXTRA_OPTS -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fmessage-length=0 -ffunction-sections -gdwarf-2 -DSS_VER=SS_VER_1_1 -DSTM32F303xC -DSTM32F3 -DSTM32 -DDEBUG -DHAL_TYPE=HAL_stm32f3 -DPLATFORM=CWLITEARM -DTINYAES128C -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/stm32f3 -I.././hal/stm32f3/CMSIS -I.././hal/stm32f3/CMSIS/core -I.././hal/stm32f3/CMSIS/device -I.././hal/stm32f4/Legacy -I.././crypto/ -I.././crypto/tiny-AES128-C -std=gnu99 -MMD -MP -MF .dep/simpleserial-aes-CWLITEARM.elf.d objdir/simpleserial-aes.o objdir/simpleserial.o objdir/stm32f3_hal.o objdir/stm32f3_hal_lowlevel.o objdir/stm32f3_sysmem.o objdir/aes.o objdir/aes-independant.o objdir/stm32f3_startup.o --output simpleserial-aes-CWLITEARM.elf --specs=nano.specs -T .././hal/stm32f3/LinkerScript.ld -Wl,--gc-sections -lm -Wl,-Map=simpleserial-aes-CWLITEARM.map,--cref   -lm

.

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

arm-none-eabi-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature simpleserial-aes-CWLITEARM.elf simpleserial-aes-CWLITEARM.hex

.

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

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

.

Creating Extended Listing: simpleserial-aes-CWLITEARM.lss

arm-none-eabi-objdump -h -S -z simpleserial-aes-CWLITEARM.elf > simpleserial-aes-CWLITEARM.lss

.

Creating Symbol Table: simpleserial-aes-CWLITEARM.sym

arm-none-eabi-nm -n simpleserial-aes-CWLITEARM.elf > simpleserial-aes-CWLITEARM.sym

Size after:

   text        data     bss     dec     hex filename

   5348         532    1484    7364    1cc4 simpleserial-aes-CWLITEARM.elf

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

+ Built for platform CW-Lite Arm (STM32F3)

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

Attack setup

CW-lite connection and target flashing

Connect to the Chipwhisperer:

In [4]:

%run "Helper_Scripts/Setup_Generic.ipynb"

Flash the target:

In [5]:

fw_path = "../hardware/victims/firmware/simpleserial-aes/simpleserial-aes-{}.hex".format(PLATFORM)
cw.program_target(scope, prog, fw_path)

Out [5]:

Detected known STMF32: STM32F302xB(C)/303xB(C)
Extended erase (0x44), this can take ten seconds or more
Attempting to program 5879 bytes at 0x8000000
STM32F Programming flash...
STM32F Reading flash...
Verified flash OK, 5879 bytes

First execution

For the DFA attack, we need a constant plaintext (and constant key of course). We could just use two bytearrays but let’s use the CW API to demonstrate its usage.

In [6]:

ktp = cw.ktp.Basic()
ktp.fixed_text = True
ktp.fixed_key = True
key, text = ktp.next()
Assuming we want to record traces, let’s capture the entire AES.
It’s useful to see which round(s) we’ll glitch by tuning scope.glitch.ext_offset later.

In [7]:

scope.clock.adc_src = "clkgen_x1"
if PLATFORM == "CWLITEXMEGA" or PLATFORM == "CW303":
    scope.adc.samples = 20000
elif PLATFORM == "CWLITEARM" or PLATFORM == "CW308_STM32F3":
    scope.adc.samples = 8000

Let’s test our setup with a first execution, without fault. It will give us the golden reference output.

In [8]:

# make sure glitches are disabled (in case cells are re-run)
scope.io.hs2 = "clkgen"

trace = cw.capture_trace(scope, target, text, key)
goldciph = trace.textout
print("Plaintext: {}".format(text.hex()))
print("Key:       {}".format(key.hex()))
print("Ciphertext:{}".format(goldciph.hex()))

Out [8]:

Plaintext: 000102030405060708090a0b0c0d0e0f
Key:       2b7e151628aed2a6abf7158809cf4f3c
Ciphertext:50fe67cc996d32b6da0937e99bafec60

In [9]:

reset_target(scope)

Just to be sure, let’s check…

In [10]:

from Crypto.Cipher import AES
aes = AES.new(bytes(key), AES.MODE_ECB)
goldciph2 = aes.encrypt(bytes(text))
print("Expected ciphertext:  {}".format(goldciph2.hex()))

Out [10]:

Expected ciphertext:  50fe67cc996d32b6da0937e99bafec60

Let’s draw the full AES execution

In [11]:

import holoviews as hv
from holoviews import opts
hv.extension('bokeh')
curve = hv.Curve(trace.wave).opts(width=600, height=600)

# add boxes around last rounds

if PLATFORM == "CWLITEXMEGA" or PLATFORM == "CW303":
    line = hv.Path([(11600, -0.25), (11600, 0.25), (13200, 0.25), (13200, -0.25), (11600, -0.25)], label='8th round').opts(color="red", show_legend=True) * \
            hv.Path([(13250, -0.25), (13250, 0.25), (14850, 0.25), (14850, -0.25), (13250, -0.25)], label='9th round').opts(color="green", show_legend=True) * \
            hv.Path([(14900, -0.25), (14900, 0.25), (16000, 0.25), (16000, -0.25), (14900, -0.25)], label='10th round').opts(color="yellow", show_legend=True)
elif PLATFORM == "CWLITEARM" or PLATFORM == "CW308_STM32F3":
    line = hv.Path([(5200, -0.1), (5200, 0.2), (5900, 0.2), (5900, -0.1), (5200, -0.1)], label='8th round').opts(color="red", show_legend=True) * \
            hv.Path([(5900, -0.1), (5900, 0.2), (6600, 0.2), (6600, -0.1), (5900, -0.1)], label='9th round').opts(color="green", show_legend=True) * \
            hv.Path([(6600, -0.1), (6600, 0.2), (6800, 0.2), (6800, -0.1), (6600, -0.1)], label='10th round').opts(color="yellow", show_legend=True)
    pass

#plt.show()
(curve * line).opts(opts.Path(line_width=3)).opts(width=600, height=600)

Out [11]: