Skip to main content
Version: 0.19.0

Trace Analysis

The collected traces are, by default, saved in the dataset folder located in the same directory as the current Jupyter notebook. The filenames are timestamped, for example: 20250519101918.zarr.

We use the scarr library to analyze waveforms. For installation instructions, refer to: Install SCARR

Run scarr for analysis​

Insert and execute the following code snippets in the Jupyter notebook to start analyzing the trace.

Define trace file path
# Replace this with the trace file you collected
trace_path = r'./dataset/20250519122059.zarr'
Import necessary dependencies
from scarr.engines.cpa import CPA as cpa
from scarr.file_handling.trace_handler import TraceHandler as th
from scarr.model_values.sbox_weight import SboxWeight
from scarr.container.container import Container, ContainerOptions
import numpy as np
Run the analysis
handler = th(fileName=trace_path) 
model = SboxWeight()
engine = cpa(model)
container = Container(options=ContainerOptions(engine=engine, handler=handler), model_positions = [x for x in range(16)])
container.run()

View Results​

After executing the code above, the trace should be analyzed and the key extracted.

View the key candidates
candidate = np.squeeze(engine.get_candidate()) # get_candidate retrieves the guessed values of the key bytes
' '.join(f"{x:02x}" for x in candidate) # Print the guessed key

This will print the extracted key
alt text

In the waveform acquisition chapter, the key used was 11 22 33 44 55 66 77 88 99 00 aa bb cc dd ee ff, which confirms the key has been successfully extracted.

import random
import time
from cracknuts.cracker import serial

cmd_set_aes_enc_key = "01 00 00 00 00 00 00 10"
cmd_aes_enc = "01 02 00 00 00 00 00 10"

aes_key = "11 22 33 44 55 66 77 88 99 00 aa bb cc dd ee ff"
aes_data_len = 16

...

Verify the Result​

We can verify the analysis result by executing the following code to check the correlation of the guessed key byte 0:

result_bytes = np.squeeze(container.engine.get_result())
Print top 10 correlated guesses for byte 0
result_0_bytes = result_bytes[0] # Get correlation values for byte 0
row_max_indices = np.argmax(np.abs(result_0_bytes), axis=1)
row_max_values = result_0_bytes[np.arange(result_0_bytes.shape[0]), row_max_indices]

top10_row_indices = np.argsort(np.abs(row_max_values))[::-1][:10]

for rank, row in enumerate(top10_row_indices, 1):
col = row_max_indices[row]
val = row_max_values[row]
print(f"Candidate {rank}: 0x{row:0X}, correlation: {val}, peak position: {col}")

The code above will print the top 10 candidate keys with the highest correlation:
alt text

tip

The results you get might differ, as long as the top one is correct, the analysis is valid.

Here you can see that byte 0 has the highest correlation with 0x11, confirming the correctness of the extracted key.

You can also visualize the correlation curve by running the code below:

Define function to plot correlation waveforms
import numpy as np
import matplotlib.pyplot as plt

def plot_correlation_peaks(bytes_index, the_key):

x = np.arange(0, 5000)

fig, ax = plt.subplots(figsize=(30, 4))

for i in range(256):
if i == the_key:
continue
ax.plot(x, result_bytes[bytes_index, i, :5000], color='gray', linewidth=0.5, alpha=0.3)

ax.plot(x, result_bytes[bytes_index, the_key, :5000], color='red', linewidth=1.0)

ax.grid(True, linestyle='--', alpha=0.3)
plt.tight_layout()

plt.show()
Plot correlation curve for byte 0 across 256 key guesses
plot_correlation_peaks(0, 0x11)

This will produce the following graph, where the correct key's correlation curve shows the most distinct peak:
alt text

You can repeat the process for other key bytes:

Plot correlation curve for byte 1 across 256 key guesses
plot_correlation_peaks(1, 0x22)

alt text

Now let’s plot the correlation curves for all 16 correct key bytes to observe their timing:

Correlation curves for all 16 correct key bytes
import matplotlib.pyplot as plt

x = np.arange(0, 5000)

fig, ax = plt.subplots(figsize=(30, 4))

ax.plot(x, result_bytes[0, 0x11, :5000], linewidth=1.0, label='0x11')
ax.plot(x, result_bytes[1, 0x22, :5000], linewidth=1.0, label='0x22')
ax.plot(x, result_bytes[2, 0x33, :5000], linewidth=1.0, label='0x33')
ax.plot(x, result_bytes[3, 0x44, :5000], linewidth=1.0, label='0x44')
ax.plot(x, result_bytes[4, 0x55, :5000], linewidth=1.0, label='0x55')
ax.plot(x, result_bytes[5, 0x66, :5000], linewidth=1.0, label='0x66')
ax.plot(x, result_bytes[6, 0x77, :5000], linewidth=1.0, label='0x77')
ax.plot(x, result_bytes[7, 0x88, :5000], linewidth=1.0, label='0x88')
ax.plot(x, result_bytes[8, 0x99, :5000], linewidth=1.0, label='0x99')
ax.plot(x, result_bytes[9, 0x00, :5000], linewidth=1.0, label='0x00')
ax.plot(x, result_bytes[10, 0xaa, :5000], linewidth=1.0, label='0xaa')
ax.plot(x, result_bytes[11, 0xbb, :5000], linewidth=1.0, label='0xbb')
ax.plot(x, result_bytes[12, 0xcc, :5000], linewidth=1.0, label='0xcc')
ax.plot(x, result_bytes[13, 0xdd, :5000], linewidth=1.0, label='0xdd')
ax.plot(x, result_bytes[14, 0xee, :5000], linewidth=1.0, label='0xee')
ax.plot(x, result_bytes[15, 0xff, :5000], linewidth=1.0, label='0xff')

ax.grid(True, linestyle='--', alpha=0.3)
ax.legend(loc='upper right', fontsize='small', ncol=2)

plt.tight_layout()

plt.show()

You can now observe the correlation coefficients for all 16 key bytes:
alt text

Congratulations! You’ve now completed the basic usage of CrackNuts. For more features and tutorials, please refer to other chapters.