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:
- the procedure we use to acquire power traces,
- a link to download the power traces, the models (Templates, Decision tree classifier and Decision tree classifier with AdaBoost), and the results we obtained and used for the article,
- all the code we use for the analysis and notebooks for explanations.
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.12 with the given requirement pacakge. We also suggest to use python virtual environment:
python3 -m venv venv
source venv/bin/activate
pip install -r code/requirements.txt
- SageMath-10.5 (here an installation tutorial, an Appimage is available for SageMath-10-7 which should work but is not tested)
For the analysis presented in the article, we used a computer with the following resources:
- RAM: 504 GB
- CPU: Intel® Xeon® Gold 5220R CPU @ 2.20GHz (96 threads)
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.
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/:
- kyber_cbd_eta1.c [$\eta_1 = 3$, Kyber-512]
- kyber_cbd_eta2.c [$\eta_1 = 2$, Kyber-768, Kyber-1024]
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:
data/chip_{1, 2, 3, 4}_eta_{1, 2}/traces.npy: raw power tracesinputs.npy: raw inputs,patterns.npy: extracted patterns of each sampled coefficient from raw power traces,ab.npy: a, b and a-b (used during the sampling of each coefficient).
models/{Tree, Template, Tree_AdaBoost}_Attack_{neg, pos}_eta{1,2}_Os_chip_{1,2,3,4}.pkl: trained models for all classifiers, positive and negative coefficients, $\eta_1 = 2\ \text{or}\ 3$ and the four chips.guesses/guesses_{1, 2, 3, 4}-{1, 2, 3, 4}_eta{1, 2}_{Tree, Template, Tree_AdaBoost}_Attack.txt: obtained guesses for each attack scenario.results/chip_{0,1,2,3}-{0,1,2,3}_Kyber-{512, 768, 1024}_{hints, methods, times, results}.npy: results output by the lattice reduction techniques.
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.