How to collect a large amount of traces?

I want to to collect many traces with different plain texts and key sequences, the number would be nearly 10**6. According to the given project such as simpleserial-des, I found that only one plain text and one key sequence would be used.
My solution is as following, that is collecting different traces into one picture. But it’s not a good solution, because the number of traces will be limited by scope.adc.samples and scope.clock.adc_src.

uint8_t encrypt_n(uint8_t* pt)
{
	uint16_t (*pts)[2];
	uint16_t *keys;
	uint16_t* ct;
	int begin = 0;
	int n = 108;
	pts = create_pt_array(n);
	keys = create_keys_array(n);
	trigger_high();
	for (int i = begin; i < n; i++) {
		ct = speck_encrypt(pts[i], keys[i], 5);
	}
	trigger_low();
	simpleserial_put('r', 16, ct);
	return 0x00;
} 
int main(void)
{
	/*uint8_t tmp[KEY_LENGTH] = {DEFAULT_KEY};*/
 
	platform_init();
	init_uart();
	trigger_setup();
	/* Uncomment this to get a HELLO message for debug */
	//putch('h');
	//putch('e');
	//putch('l');
	//putch('l');
	//putch('o');
	//putch('\n');
 
	simpleserial_init();
	simpleserial_addcmd('k', 16, get_key);
	simpleserial_addcmd('p', 16,  encrypt_n);
	simpleserial_addcmd('x',  0,   reset);
	while(1)
	simpleserial_get();
}

So how to collect many traces, and put them into different pictures? I believe that something should be change in the following codes which in cw_pro_aes.ipynb:

def capture_trace(_ignored=None):
    ktp = cw.ktp.Basic()
    key, text = ktp.next()
    return cw.capture_trace(scope, target, text, key).wave
wave = capture_trace()
print("✔️ OK to continue!")

from tqdm.notebook import tnrange
import numpy as np
traces = []
for i in tnrange(1, desc='Capturing traces'):
    key, text = ktp.next()
    trace = cw.capture_trace(scope, target, text, key)
    if trace is None:
        continue
    traces.append(trace.wave)
traces = np.array(traces)

By the way, how does ktp.next() work? Can it load key and text from an existing dataset? And, how does scope, target, text, key in cw.capture_trace(scope, target, text, key) connect to my C function encrypt_n()? Thanks.

It sounds like you want to speed up the trace acquisition rate. What is your capture device? If you have a Husky, this is easily doable. Otherwise, maybe not.

In our example AES notebooks, there are two things that limit the capture rate:

  1. The plaintext to encrypt is sent to the target for each encryption. This takes a long time relative to the time it takes for the target to actually do the encryption.
  2. The scope must be armed prior to each capture; this means there is Python code running at each iteration of the capture loop, which slows things down a lot.

Husky provides an easy way around #2, with segmented capture (explained here).

On CW-lite/pro, there is a similar feature but it is buggy and not supported; see here.

Point #1 is easily handled in the target firmware (regardless of the capture device); our AES example firmware already shows one way to do this, in the enc_multi_getpt() function: it does num_encryption_rounds encryptions when the f command is sent. Here the first plaintext is the plaintext provided by the user, as usual; the resulting ciphertext is used as the plaintext for the next encryption, and so on.

We do this because the AES attacks that we demonstrate use a fixed key and a variable (known) plaintext. There are other ways to do this; this is just one.

This ties into your ktp question. The source is pretty self-explanatory; by default, ktp.next() returns a fixed key and a random plaintext.

Finally, the source for capture_trace() is also straightforward; this sends the p command to your target, and this line in your target is what connects that to encrypt_n():
simpleserial_addcmd('p', 16, encrypt_n);

1 Like

Thanks for your answer! My capture device is CW-Lite, target is STM32F303. However, my main question is how to collect a large amount of traces pictures. Assuming that I have 10000 plaintexts and 10000 keys, if running encrypt function for each of them, we can get 10000 traces. Then how to use the notebook (or maybe through changing C code) to achieve “running encrypt function for each of them and getting 10000 traces”? Thanks.

Sounds like I misunderstood your original question. To collect more traces, just collect more traces! There is no magic here.

So for example, if we look at the sca101/Lab 4_3 hardware notebook, traces are captured in this loop:

N = 50
for i in trange(N, desc='Capturing traces'):
    key, text = ktp.next()
    trace = cw.capture_trace(scope, target, text, key)
    if not trace:
        continue
    
    proj.traces.append(trace)

This captures N=50 traces. Set N to the number of traces you would like.
key and text can be whatever you would like for each trace; you can use ktp.next() or any other scheme of your choice to set the key and plaintext for each trace.

If you haven’t already, I encourage you to go through our full set of course notebooks. This will teach you the basics of using the ChipWhisperer platform.

1 Like

Thanks for your help! I have gone through sca101/Lab 4_3 hardware notebook, but I’m still confused by some details of the code, especially the connection between C codes and notebook script.
I notice that there are four variables in cw.capture_trace(scope, target, text, key), that are scope, target, text and key. However, when checking the C code, for example, simpleserial-aes.c, I think that the main function will call the aes function. Here is aes function:

#if SS_VER == SS_VER_2_1
uint8_t aes(uint8_t cmd, uint8_t scmd, uint8_t len, uint8_t *buf)
{
    uint8_t req_len = 0;
    uint8_t err = 0;
    uint8_t mask_len = 0;
    if (scmd & 0x04) {
        // Mask has variable length. First byte encodes the length
        mask_len = buf[req_len];
        req_len += 1 + mask_len;
        if (req_len > len) {
            return SS_ERR_LEN;
        }
        err = get_mask(buf + req_len - mask_len, mask_len);
        if (err)
            return err;
    }

    if (scmd & 0x02) {
        req_len += 16;
        if (req_len > len) {
            return SS_ERR_LEN;
        }
        err = get_key(buf + req_len - 16, 16);
        if (err)
            return err;
    }
    if (scmd & 0x01) {
        req_len += 16;
        if (req_len > len) {
            return SS_ERR_LEN;
        }
        err = get_pt(buf + req_len - 16, 16);
        if (err)
            return err;
    }

    if (len != req_len) {
        return SS_ERR_LEN;
    }

    return 0x00;

}
#endif

I found that the former varibles scope, target, text and key didn’t occur.
So here is my questions:

  1. How do the varibles scope, target, text and key reflect in (or sent to) the C codes aes?
  2. If I declare my own text and key, maybe loaded from files, how can I make sure that my text and key are send to the C codes aes correctlly?

Thanks for your help!!

scope, target and text are Python objects in code that’s running on your host PC.

simpleserial-aes.c gets compiled into the firmware that’s running on your STM32 target board.

You won’t find references to the host Python code in the target firmware (or vice-versa); those are two separate worlds. These two worlds communicate via the UART link on the TIO1/TIO2 pins using our SimpleSerial protocol.

To understand how everything works together, start at the beginning with these two notebooks:
0 - Introduction to Jupyter Notebooks.ipynb
1 - Connecting to Hardware.ipynb

Then move onto courses/sca101. Take your time and make sure you understand every step.