International Association for Cryptologic Research

International Association
for Cryptologic Research

Transactions on Cryptographic Hardware and Embedded Systems 2025

Avengers assemble! Supervised learning meets lattice reduction:

A single power trace attack against CRYSTALS-Kyber Key Generation


README

Avengers assemble!

The current Git repository contains the artifacts of the article accepted at CHES 2025 (4th issue, pdf available here) :

Avengers assemble!
Supervised learning meets lattice reduction

A single power trace attack against CRYSTALS-Kyber Key Generation

Pierre-Alain Fouque1, Damien Marion1 , Quyen Nguyen2 Alexandre Wallet3

1Univ Rennes, CNRS, Inria, IRISA, Rennes, France {pierre-alain.fouque,damien.marion}@irisa.fr
2IDEMIA Secure Transactions, Courbevoie, France, [email protected]
3PQShield Ltd., Oxford, United Kingdom, [email protected]

Introduction and prerequisite

The Git repository contains:

All the acquisitions and analyses have been done and tested only on Ubuntu 24.04. To be able to reproduce our work, you need:

python3 -m venv venv
source venv/bin/activate
pip install -r code/requirements.txt

For the analysis presented in the article, we used a computer with the following resources:

However, for training and analysis, 30 GB of RAM is sufficient. Training and classification take around 5 hours and testing around 1h. The time needed for the solving step (lattice reduction) will depend on the number of threads you can use as well as the time limit (default 18h)
you give to the script.

See the Data section to download the provided data.

Acquisition

In this section we provide the instructions to acquire power traces as we used for the paper. To do so, you need a CW1200 ChipWhisperer-Pro (on the following photo, the screen and the case have been removed) and a CW308 UFO with an STMF32F4 plugged in. To fully reproduce the acquisition setup you will need four of this targets.

Description

installation of LIP6DROMEL

Our acquisition relies on LIP6DROMEL. Please follow the installation procedure available on the LIP6DROMEL GitLab repository: https://gitlab.lip6.fr/heydeman/lip6dromel/

Source:

The source code files we used from Kyber are available in the directory targets/:

Theses files must be copied in target_prog/apps/ (lip6dromel repository).

Modifications:

You must change the inputs size in core/lip6dromel.h (from 8 to 48):

[line 57] #define NUM_INPUTS 48

Additionally the file core/stm32f4/stm32f4_hal.c must be modified to get similar shape traces as we get initially (from 0 to 5). This has been documented in the article "Cycle-Accurate Power Side-Channel Analysis Using the ChipWhisperer: a Case Study on Gaussian Sampling":

[line 92] HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_ACR_LATENCY_5WS); //wait states not needed for HSE

To be automatically compiled you must add the following line in /target_prog/apps/metadata.yml:

kyber_cbd_eta1:
  Name: "Kyber sampler for eta_1 = 3"
  Description: "Code from the official github of Kyber"
  NbTraces: { "*": 20000 }

kyber_cbd_eta2:
  Name: "Kyber sampler for eta_1 = 2"
  Description: "code from the official github of Kyber"
  NbTraces: { "*": 20000 }

Then you must recompile

make TARGETS=smt32f4 binaries
make setup

Acquisition:

Below you have the command line that you must run (in the directory where you clone LIP6DROMEL) to record your own power traces.
It will record 20k power traces (in traces/chip_{1, 2, 3, 4}_eta_{1, 2}/traces.npy), and the input (in traces/chip_{1, 2, 3, 4}_eta_{1, 2}/inputs.npy). For each chip, run the corresponding command.

###############################################
## CHIP 1
## traces for eta_1 [eta1 = 3]
bin/server.py --check-target=stm32f4 \
 -b traces/chip_1_eta_1/ \
 -f build/apps/stm32f4/kyber_cbd_eta1.hex \
 --seed rng,1187965042,762856188\
 --input -c 20000

## traces for eta_2 [eta1 = 2]
bin/server.py --check-target=stm32f4 \
 -b traces/chip_1_eta_2/ \
 -f build/apps/stm32f4/kyber_cbd_eta1.hex \
 --seed rng,2013430964, 2658804226\
 --input -c 20000

###############################################
## CHIP 2
## traces for eta_1 [eta1 = 3]
bin/server.py --check-target=stm32f4 \
 -b traces/chip_2_eta_1/ \
 -f build/apps/stm32f4/kyber_cbd_eta1.hex \
 --seed rng,3511463748,307861468\
 --input -c 20000

## traces for eta_2 [eta1 = 2]
bin/server.py --check-target=stm32f4 \
 -b traces/chip_2_eta_2/ \
 -f build/apps/stm32f4/kyber_cbd_eta1.hex \
 --seed rng,2663346444, 3917115617\
 --input -c 20000

###############################################
## CHIP 3
## traces for eta_1 [eta1 = 3]
bin/server.py --check-target=stm32f4 \
 -b traces/chip_3_eta_1/ \
 -f build/apps/stm32f4/kyber_cbd_eta1.hex \
 --seed rng,2623678540,4173713707\
 --input -c 20000

## traces for eta_2 [eta1 = 2]
bin/server.py --check-target=stm32f4 \
 -b traces/chip_3_eta_2/ \
 -f build/apps/stm32f4/kyber_cbd_eta1.hex \
 --seed rng,1388891198, 532043275\
 --input -c 20000

###############################################
## CHIP 4
## traces for eta_1 [eta1 = 3]
bin/server.py --check-target=stm32f4 \
 -b traces/chip_4_eta_1/ \
 -f build/apps/stm32f4/kyber_cbd_eta1.hex \
 --seed rng,1754615457,2407128966\
 --input -c 20000

## traces for eta_2 [eta1 = 2]
bin/server.py --check-target=stm32f4 \
 -b traces/chip_4_eta_2/ \
 -f build/apps/stm32f4/kyber_cbd_eta1.hex \
 --seed rng,2411590745, 246036733\
 --input -c 20000

Data

You can download here (v1.2 -- inputs.npy files were missing in the previous versions) all the data we generated for the paper:

The file names start with the number of the training chip, followed by 'to' and then the number of the testing chip.

Analysis

Repository organisation

Avengers assembleproject
│   README.md
│   requierement.txt
|
└───code/
|   └───python/
|   |   |   {tree, template, tree_adaBoost}_attack.py [classifiers classes]
|   |   |   analysis.py  [analysis to run for training and testing]
|   |   |   bkz_display.py  [used by bkz.ipynb]
|   |   |   estimator.py  [used by bkz.ipynb]
|   |   |   models.py  [leakage models engines]
|   |   |   patterns.py [patterns computation and loading, used by pre-processing.ipynb]
|   |
|   └───notebook/
|       |   pre-processing.ipynb
|       |   bkz.ipynb
|   └───sage/
|       |   solve.sage

[do be downloaded or recomputed]
└───data/
│   └───chip_{1, 2, 3, 4}_eta_{1, 2}/
│       │   traces.npy
│       │   inputs.npy
│       │   patterns.npy
|       |   ab.npy
│
└───models
|    │   {Tree, Template, Tree_AdaBoost}_Attack_{neg, pos}_eta{1,2}_Os_chip_{1,2,3,4}.pkl
|    │   {Tree_AdaBoost}_Attack_{neg, pos}_eta{1,2}_Os_chip_{1-2-3, 2-3-4, 3-4-1, 4-1-2}.pkl
|
└───guesses
|    |   guesses_{1, 2, 3, 4}-{1, 2, 3, 4}_eta{1, 2}_{Tree, Template, Tree_AdaBoost}_Attack.txt
|    |   guesses_{1-2-3, 2-3-4, 3-4-1, 4-1-2}-{1, 2, 3, 4}_eta{1, 2}_{Tree, Template, Tree_AdaBoost}_Attack.txt
|
└───results
|    |   chip_{1, 2, 3, 4}-{1, 2, 3, 4}_Kyber-512_{methods, hints}_1000_1.txt
|    |   chip_{1, 2, 3, 4}-{1, 2, 3, 4}_Kyber-768_{methods, hints}_666_1.txt
|    |   chip_{1, 2, 3, 4}-{1, 2, 3, 4}_Kyber-1024_{methods, hints}_500_1.txt
|    |   chip_{1-2-3, 2-3-4, 3-4-1, 4-1-2}-{1, 2, 3, 4}_Kyber-512_{methods, hints}_1000_1.txt
|    |   chip_{1-2-3, 2-3-4, 3-4-1, 4-1-2}-{1, 2, 3, 4}_Kyber-768_{methods, hints}_666_1.txt
|    |   chip_{1-2-3, 2-3-4, 3-4-1, 4-1-2}-{1, 2, 3, 4}_Kyber-1024_{methods, hints}_500_1.txt
|
[need to be recomputed]
└───figs
|    |   Kyber-1024 (500 instances).png
|    |   Kyber-512 (1000 instances).png
|    |   Kyber-768 (666 instances).png
|    |   beta.png

Pre-processing

All the pre-processing procedures are available in code/notebook/pre-processing.ipynb.

Training and testing

The training and testing processes are done by code/python/analysis.py. Arguments are written at the beginning of the main function. If you have downloaded the data, put them in the right place or change the paths. If models are not
available, they will be computed. Figures will be generated during the testing process, and save in fig/. After checking the arguments, you can run:

python analysis.py

Solving

The lattice reduction process is done by code/sage/solve.sage. Arguments are written at the beginning of the main function. After checking them, you can run the process:

sage solve.sage

If results already exist in the given results directory (by default results/), they will no be recomputed.
To visualize the results after solving, you can have a look at code/notebook/bkz.ipynb.

Note

Results and figures may slightly differ from those in the paper; this is solely due to some updates in the code (seed and reordering) and re-execution for the artifact submission. However, they remain consistent and do not alter the conclusions.