ChipWhisperer教程(二)

发布于:2025-06-14 ⋅ 阅读:(21) ⋅ 点赞:(0)

——CW308-STM32F目标板的波形采集

一、目标板介绍

CW308 UFO 板是用于攻击各种嵌入式目标的便捷主板,CW308-STM32F 目标板是在 CW308 UFO 主板上搭载了一个 STM32F303 处理器的目标板,本文将其简称为 STM32F303 目标板。STM32F303 是 STMicroelectronics 推出的一款基于 ARM Cortex-M4 内核的32位微控制器系列。该系列微控制器专为需要高性能和低功耗的应用而设计,广泛应用于工业控制、消费电子、医疗设备、通信设备等领域。具有以下特点:

  1. ARM Cortex-M4内核:最高主频可达 72 MHz;支持多种低功耗模式,适合电池供电的应用;支持单精度浮点运算,适用于需要高精度计算的应用。
  2. 丰富的外设:多个通用定时器和高级控制定时器;多个 USART、SPI、I2C 和 CAN 接口;多个 ADC 和 DAC,支持高精度模拟信号处理;内置温度传感器和电压参考源。
  3. 存储器:Flash 存储器,从 64 KB 到 256 KB 不等;SRAM,从 16 KB 到 48 KB 不等。
  4. 低功耗特性:支持多种低功耗模式,包括睡眠模式、停止模式和待机模式;在待机模式下,功耗极低,适合电池供电的应用。

二、目标板代码编写

STM32F303目标板的代码一般包括为Makefile文件、.c源文件和.h头文件,三者放在同一个工程目录下。

1. Makefile文件的编写

Makefile文件编写比较简单,且相对固定,这里给一个模板,用户可以根据需要简单修改:

#定义一个编译器生成编译过程文件的文件名,可以自行更改
TARGET = stm32f303-aes-normal

#添加工程目录下的.c文件
SRC += $(wildcard ./*.c)
#添加src文件夹下存放的.c文件,src为工程目录下的文件夹
SRC += $(wildcard src/*.c)
#添加include头文件目录,include文件夹在工程目录下,其中存放了.h头文件
EXTRAINCDIRS += include/

#firmware文件夹的路径,请查看ChipWhisperer的安装路径,本文安装在了D:/App路径下
FIRMWAREPATH = D:/App/ChipWhisperer5_64/cw/home/portable/chipwhisperer/hardware/victims/firmware/.

#后面的命令比较固定,一般不用更改
EXTRA_OPTS = NO_EXTRA_OPTS
CFLAGS += -D$(EXTRA_OPTS)
CRYPTO_TARGET=NONE
SRC_DIRS := $(addprefix objdir-$(PLATFORM)/, $(dir $(SRC)))
$(shell mkdir -p $(SRC_DIRS))
${info Building for platform ${PLATFORM} with CRYPTO_TARGET=$(CRYPTO_TARGET)}
include $(FIRMWAREPATH)/simpleserial/Makefile.simpleserial
include $(FIRMWAREPATH)/Makefile.inc

2. 通信协议接口文件的编写

目标板和捕获板通过简单串口通信,目标板上程序应符合这个通信规范,常用的通信规范包括两种,分别是Simpleserial v1.1和Simpleserial v2.1,两者区别主要包括每次传输的字节数不同和命令回调函数的参数不同,其他不同点请参考官网:https://chipwhisperer.readthedocs.io/en/latest/simpleserial.html​。通信过程可以简单描述为:电脑主机通过jupyter notebook上的采集API让捕获板发送一个数据包,数据包中主要包括一个字节的命令、可变长度的数据和数据的长度,目标板接收到命令后会调用对应命令的回调函数,执行完毕后可以将结果发送到捕获板中,等待主机执行读取捕获板缓冲区数据的API。

串口通信规范体现在代码上主要包括头文件及全局变量的添加、硬件初始化、命令的添加、以及命令回调函数的编写,下面分别进行说明。

上面对与接口有关的内容进行说明,建议将该部分单独定义成一个文件,本文将其命令为simpleserial-aes.c,而函数的真正执行过程定义在其他文件中。

  • 头文件及全局变量

    头文件及全局变量的添加相对固定,下面是一个模板:

    //添加相对固定的头文件
    #include <stdint.h>
    #include <stdlib.h>
    #include "hal.h"
    #include "simpleserial.h"
    #include "stm32f3xx.h"
    
    //自行添加添加与待采密码算法运算相关的头文件
    #include "aes.h"
    
    //添加一些命令名称
    #define	CMD_ENCRYPT 0x00	//AES加密
    #define	CMD_KEYEXP 0x01		//AES密钥扩展
    #define	CMD_GETCYCLE 0x02	//获取加密运行的时期周期
    
    //定义全局变量
    uint32_t clk_cycle;
    
  • 命令回调函数

    Simpleserial v1.1和Simpleserial v2.1的命令执行函数的参数类型不同,为了同时兼容这两种协议,可以采用条件编译。当然,也可以忽略Simpleserial v1.1通信协议,因为Simpleserial v2.1能够传输的字节数更多,在编写采集程序时统一采用Simpleserial v2.1协议。

    //定义密钥扩展命令的回调函数
    #if SS_VER == SS_VER_2_1
    uint8_t set_key(uint8_t cmd, uint8_t scmd, uint8_t len, uint8_t *main_key)	//Simpleserial v2.1协议的写法
    #else
    uint8_t set_key(uint8_t* main_key, uint8_t len)	//Simpleserial v1.1协议的写法
    #endif
    {
    	key_expansion(main_key);	//这里将真正的执行函数定义在其他文件中,并在aes.h中声明
    	return 0x00;	//注意统一返回0x00,表示命令执行成功
    }
    
    //定义加密命令的回调函数
    #if SS_VER == SS_VER_2_1
    uint8_t get_cipher(uint8_t cmd, uint8_t scmd, uint8_t len, uint8_t *data)
    #else
    uint8_t get_cipher(uint8_t* data, uint8_t len)
    #endif
    {
    	uint32_t start, end;
    	uint8_t cipher[16]={0};
    
    	trigger_high();			//将触发信号拉高,开始采波
    	start = DWT->CYCCNT;	//获取开始执行时的时钟数
    
    	encrypt(data, cipher);	//真正执行加密的函数
    
    	end = DWT->CYCCNT;		//获取结束执行时的时钟数
    	clk_cycle = end - start;//获取执行加密花费的时钟周期数
    	trigger_low();			//触发信号拉低,结束采集
    
    	simpleserial_put('r', 16, cipher);	//向捕获板发送加密结果,16表示16个字节,'r'为固定的命令名称
    
    	return 0x00;			//别忘了返回0x00
    }
    
    //定义获取时钟周期命令的回调函数
    #if SS_VER == SS_VER_2_1
    uint8_t get_cycle(uint8_t cmd, uint8_t scmd, uint8_t len, uint8_t *x)
    #else
    uint8_t get_cycle(uint8_t* x, uint8_t len)
    #endif
    {
    	uint8_t res[4]={0};
    
    	//将32位的字转换为4个字节存储到res中
    	res[0]=(clk_cycle>>24)&0xff;
    	res[1]=(clk_cycle>>16)&0xff;
    	res[2]=(clk_cycle>>8)&0xff;
    	res[3]=clk_cycle&0xff;
    
    	//向捕获板发送4个字节的时钟周期
    	//注意向发送数据时一定要先转换为字节数组,并标明字节数组的大小
    	simpleserial_put('r', 4, res);
    	return 0x00;
    }
    
  • 硬件初始化及命令的添加

    这两个过程均在主函数中执行,且格式相对固定,下面给出对应模板:

    //初始化ARM Cortex-M微控制器中的数据观察点和跟踪(DWT)模块。
    //DWT模块提供了多种调试功能,包括周期计数器 (CYCCNT),它可以用于精确测量代码执行时间。
    void DWT_Init(void) {
    	CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    	DWT->CYCCNT = 0;
    	DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
    }
    
    int main(void)
    {
    	//硬件初始化,写法固定,一般不用更改
        platform_init();
    	init_uart();
    	trigger_setup();
    	DWT_Init();
    	simpleserial_init();
    
    	//添加命令(单个字节表示)对应的回调函数
    	//命令名可以定义专门的宏定义,也可以用'p'、'k'等字符表示,或者直接写一个数字,本文推荐定义宏定义
    	//中间的数字表示输入数据的长度
    	simpleserial_addcmd(CMD_KEYEXP, 16, set_key);
    	simpleserial_addcmd(CMD_ENCRYPT, 16, get_cipher);
    	simpleserial_addcmd(CMD_GETCYCLE, 0, get_cycle);
    
    	//循环接收捕获板发来的命令,并调用对应回调函数
    	while(1)
    		simpleserial_get();
    }
    

3. 密码算法的c语言实现

本文给出实现aes算法的实现示例,包括aes.h和aes.c两个文件。

/*
 * aes.h文件,定义的S盒,对密钥扩展和加密函数进行了声明
*/

#ifndef AES_H
#define AES_H

#include <stdint.h>
#include <stdlib.h>

static const uint8_t AES_SBOX[256]={
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
	0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
	0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
	0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
	0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
	0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
	0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
	0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
	0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
	0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
	0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
	0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
	0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 
	0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
	0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
	0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
};

void key_expansion(uint8_t* main_key);

void encrypt(uint8_t* plaintext,uint8_t* ciphertext);

#endif
/*
 * aes.c文件,定义的密钥扩展和加密函数
*/

#include "aes.h"

uint8_t round_key[176]={0};	//全局变量,保存密钥扩展后得到的轮密钥

static inline uint8_t gm2(uint8_t a){
    uint8_t t0=a<<1;
    if(a>>7==1)
        return t0^0x1b;
    return t0;
}

static inline uint8_t gm3(uint8_t a){
    return gm2(a)^a;
}

static void sub_bytes(uint8_t state[16]){
    for(uint8_t i=0;i<16;i++){
        state[i]=AES_SBOX[state[i]];
    }
}

static void shift_rows(uint8_t state[16]){
    uint8_t t0,t1;
    t0=state[1];state[1]=state[5];state[5]=state[9];state[9]=state[13];state[13]=t0;
    t0=state[2];t1=state[6];state[2]=state[10];state[6]=state[14];state[10]=t0;state[14]=t1;
    t0=state[15];state[15]=state[11];state[11]=state[7];state[7]=state[3];state[3]=t0;
}

static void mix_columns(uint8_t state[16]){

    uint8_t i, j, col[4], res[4];

    for (j = 0; j < 4; j++) {
        for (i = 0; i < 4; i++)
            col[i] = state[i+4*j];

        res[0] = gm2(col[0])^gm3(col[1])^col[2]^col[3];
        res[1] = col[0]^gm2(col[1])^gm3(col[2])^col[3];
        res[2] = col[0]^col[1]^gm2(col[2])^gm3(col[3]);
        res[3] = gm3(col[0])^col[1]^col[2]^gm2(col[3]);

        for (i = 0; i < 4; i++)
            state[i+4*j] = res[i];
    }
}

static void add_round_key(uint8_t state[16], uint8_t round) {
    for (uint8_t i = 0; i < 16; i++) {
        state[i] ^= round_key[16*round+i];
    }
}

void key_expansion(uint8_t* main_key){
    uint8_t RC[11]={0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1B,0x36};
    uint8_t tmp[4];

    for (uint8_t i = 0; i < 16; i++)
        round_key[i] = main_key[i];

    for (uint8_t i = 4; i < 44; i++) {
        tmp[0] = round_key[4*(i-1)+0];
        tmp[1] = round_key[4*(i-1)+1];
        tmp[2] = round_key[4*(i-1)+2];
        tmp[3] = round_key[4*(i-1)+3];

        if (i%4 == 0) {
            uint8_t t[4]={tmp[0],tmp[1],tmp[2],tmp[3]};
            tmp[0] =AES_SBOX[t[1]]^RC[i/4];
            tmp[1] =AES_SBOX[t[2]];
            tmp[2] =AES_SBOX[t[3]];
            tmp[3] =AES_SBOX[t[0]];
        }

        round_key[4*i+0] = round_key[4*(i-4)+0]^tmp[0];
        round_key[4*i+1] = round_key[4*(i-4)+1]^tmp[1];
        round_key[4*i+2] = round_key[4*(i-4)+2]^tmp[2];
        round_key[4*i+3] = round_key[4*(i-4)+3]^tmp[3];
    }
}

void encrypt(uint8_t* plaintext,uint8_t* ciphertext){
    uint8_t state[16];

    for (uint8_t i = 0; i < 16; i++) {
        state[i] = plaintext[i];
    }

    for (uint8_t round = 0; round < 10; round++) {
        add_round_key(state, round);
        sub_bytes(state);
        shift_rows(state);
        if(round!=9)mix_columns(state);
    }
    add_round_key(state, 10);

	for (uint8_t i = 0; i < 16; i++) {
        ciphertext[i] = state[i];
    }
}

三、采集代码编写

采集代码为运行在jupyter notebook上的代码,ChipWhipserer官方已经给了非常详细的说明,下载网址为https://github.com/newaetech/chipwhisperer-jupyter​。这里给一个比较标准的STM32F303目标板采集模板,并进行补充说明。

#捕获板的类型,Lite、Pro和Husky类型捕获板均为'OPENADC'
SCOPETYPE = 'OPENADC'
#目标板平台类型,本文采用搭载STM32F3处理器的CW308 UFO目标板
PLATFORM = 'CW308_STM32F3'
#串口通过协议类型,建议使用'SS_VER_2_1'
SS_VER='SS_VER_2_1'
#对捕获板和目标板对象进行初始化,写法比较固定

import chipwhisperer as cw
import time

try:
    if not scope.connectStatus:
        scope.con()
except NameError:
    scope = cw.scope()

if SS_VER == "SS_VER_2_1":
    target_type = cw.targets.SimpleSerial2
else:
    target_type = cw.targets.SimpleSerial

try:
    target = cw.target(scope, target_type)
except:
    print("INFO: Caught exception on reconnecting to target - attempting to reconnect to scope first.")
    print("INFO: This is a work-around when USB has died without Python knowing. Ignore errors above this line.")
    scope = cw.scope()
    target = cw.target(scope, target_type)

time.sleep(0.1)
scope.default_setup()

#注意这里返回的是STM32F处理器的程序烧写对象,其他类型的处理器需要自行修改
#AVRProgrammer/NEORV32Programmer/SAM4SProgrammer/STM32FProgrammer/XMEGAProgrammer/iCE40Programmer
prog = cw.programmers.STM32FProgrammer

print("INFO: Found ChipWhisperer😍")
#编译目标板代码,这里将目标板代码放在了D:/mycode/chipwhisperer/AES/stm32f-normal目录下
%%sh -s "$SCOPETYPE" "$PLATFORM" "$SS_VER"
cd D:/mycode/chipwhisperer/AES/stm32f-normal	#用户自行更改
make SCOPETYPE=$1 PLATFORM=$2 SS_VER=$3 CRYPTO_TARGET=NONE -j
#烧写.hex文件到目标板中
cw.program_target(scope, prog, "D:/mycode/chipwhisperer/AES/stm32f-normal/simpleserial-aes-{}.hex".format(PLATFORM))
#这里可以对一些捕获板的参数进行设置
scope.clock.adc_mul=4		#每个时钟周期采集的点数,默认为4
scope.gain.db=20			#低噪声放大器的增益,默认为25,当波形失真时可以减小该值,一般在[0,25]之间调整
scope.adc.samples = 10000	#采样点数
scope.adc.offset =0			#采样位置的偏移
#这里先采一条波形进行测试,观察代码是否运行正常

%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
import time

#定义命令,建议和目标板接口文件simpleserial-aes.c的宏定义保持一致
CMD_ENCRYPT=0x00
CMD_KEYEXP=0x01
CMD_GETCYCLE=0x02

#定义明文和密钥
plaintext=bytearray([0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff])
key=bytearray([0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f])

#将主密钥写入目标板中并进行密钥扩展
target.simpleserial_write(CMD_KEYEXP, key)
time.sleep(0.1)

#将明文写入目标板中并进行加密
target.flush()	#刷新缓冲区
scope.arm()		#准备采波
target.simpleserial_write(CMD_ENCRYPT, plaintext)	#执行加密算法
ret = scope.capture()	#捕获波形,默认为遇到上升沿开始采波
if ret:
    print("Failed capture")
wave=np.array(scope.get_last_trace())	#读出采集的波形
cipher = target.simpleserial_read('r', 16)	#读取加密结果
print('密文:',cipher.hex())  #正确结果为:69c4e0d86a7b0430d8cdb78070b4c55a

time.sleep(0.1)

#读取运行的时钟周期
target.flush()
target.simpleserial_write(CMD_GETCYCLE, bytearray())
res = target.simpleserial_read('r', 4)		#读取的是一个4字节bytearray
cycle=(int)(res[0]<<24)|(res[1]<<16)|(res[2]<<8)|res[3]		#转换为int类型数字
print('花费时钟周期:',cycle)

#显示波形
plt.plot(wave)
plt.show()
#开始正式采波

from tqdm.notebook import trange
import numpy as np

#可以根据上面的观察结果重新调整采集的点数和偏移
scope.adc.samples = 2500
scope.adc.offset =0

#如果没有运行上一步这里需要对命令进行声明
CMD_ENCRYPT=0x00
CMD_KEYEXP=0x01

#采集60000条曲线
N=60000
plaintext=[]
ciphertext=[]
traces=[]

#先将主密钥定入目标板并进行密钥扩展
key=bytearray([0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10])
target.simpleserial_write(CMD_KEYEXP, key)
time.sleep(0.1)
target.flush()

#开始采集
for i in trange(N, desc='Capturing traces'):
    plain=np.random.randint(256,size=16,dtype=np.uint8)	#生成一个16字节随机明文
    plaintext.append(plain)
    scope.arm()
    target.simpleserial_write(CMD_ENCRYPT, bytearray(plain))
    ret = scope.capture()
    if ret:
        raise RuntimeError("Failed capture!")
    wave=np.array(scope.get_last_trace())
    traces.append(wave)
    cipher = target.simpleserial_read('r', 16)
    ciphertext.append(np.array(cipher,dtype=np.uint8))
#保存采集结果,建议均保存为.npy文件,读写速度较快,且可以直接读取为numpy数组,无需二次转化
#如果需要和其他非python程序交互,建议保存为二进制文件,如果保存为txt、csv等文本类型,则保存和读取速度均较慢

#保存能量曲线
traces_path="D:/WMD_DATA/aes_normal_traces.npy"
np.save(traces_path,np.array(traces))

#保存明文
plaintext_path="D:/WMD_DATA/aes_normal_plaintext.npy"
np.save(plaintext_path,np.array(plaintext,np.uint8))

#保存密文
ciphertext_path="D:/WMD_DATA/aes_normal_ciphertext.npy"
np.save(ciphertext_path,np.array(ciphertext,np.uint8))
#每次采集完毕记得断开和板子的连接
#如果不执行下面的函数,在其他jupyter notebook文件中连接时会出错,此时需要将板子断电
scope.dis()
target.dis()

四、补充

如果想直接将保存的数据保存为.trs格式的文件,需要进行以下步骤:

1. 安装trsfile库

在jupyter notebook文件中,执行下面的命令:

%%sh
pip install --upgrade pip	#更新pip
pip install trsfile			#安装trsfile库

当然也可以直接打开ChipWhisperer Bash,然后执行pip install trsfile,执行后记得重启ChipWhisperer。

2. 将数据写入.trs文件的模板函数:

import os
import numpy as np
from trsfile import trs_open, Trace, SampleCoding, Header
from trsfile.parametermap import TraceParameterMap, TraceParameterDefinitionMap
from trsfile.traceparameter import ParameterType, TraceParameterDefinition

_type_to_ParameterType={
    'bool':'BOOL',
    'int8':'BYTE','uint8':'BYTE',
    'int16':'SHORT','uint16':'SHORT',
    'int32':'INT','uint32':'INT',
    'int64':'LONG','uint64':'LONG',
    'float16':'FLOAT','float32':'FLOAT',
    'float64':'DOUBLE',
    'str':'STRING','bytes':'STRING'
}
_type_to_SampleCoding = {
        "int8": 'BYTE', "uint8": 'BYTE',
        "int16": 'SHORT', "uint16": 'SHORT',
        "int32": 'INT', "uint32": 'INT',
        "int64": 'INT', "uint64": 'INT',
        "float32": 'FLOAT', "float64": 'FLOAT'
}

def save_trs(header,traces,param:dict, trs_file_path:str):
    header = {Header[key]: value for key, value in header.items()}
    parameter_def,param_offset = {},0
    for key in param:
        param[key]=np.array(param[key],np.uint8)
        param_len=param[key].shape[1]
        param_type=ParameterType[_type_to_ParameterType[param[key].dtype.name]]
        parameter_def[key] = TraceParameterDefinition(param_type, param_len, param_offset)
        param_offset += param_len*param_type.byte_size
    header[Header.LENGTH_DATA] = param_offset
    header[Header.TRACE_PARAMETER_DEFINITIONS] = TraceParameterDefinitionMap(parameter_def)
    if not isinstance(traces,np.ndarray):
        traces=np.array(traces)
    if traces.dtype.name.endswith('64'):
        traces_type_new=traces.dtype.name.replace('64','32')
        traces=np.array(traces,np.dtype(traces_type_new))
    trs_type=SampleCoding[_type_to_SampleCoding[traces.dtype.name]]
    if os.path.exists(trs_file_path):
        os.remove(trs_file_path)
    header[Header.NUMBER_TRACES]=0
    with trs_open(trs_file_path,'w',headers=header,live_update=True) as trs_file:
        for i in range(traces.shape[0]):
            trs_param={}
            for key,value in parameter_def.items():
                trace_parameter=value.param_type.param_class
                trs_param[key]=trace_parameter(param[key][i])
            trs_file.extend(Trace(trs_type,traces[i],TraceParameterMap(trs_param)))

该函数较长,且一般无需更改,可以将其保存为一个文件,本文命名为trsfile_save.py,保存在D:/mycode/chipwhisperer/utils下。

3. 修改jupyter notebook中的保存文件部分:

%run D:/mycode/chipwhisperer/utils/trsfile_save.py #通过魔法函数%run执行trsfile_save.py文件

#定义trs文件保存路径
trsfile_path="D:/WMD_DATA/stm32f_aes_normal.trs"

#创建trs文件的头,里面可以定义一些有用的信息,具体可通过执行help(Header)查看
header={
	#这里定义了其中的'DESCRIPTION'参数,指明了采集目标,以及主密钥
	'DESCRIPTION':'target: STM32F303, AES normal; main key:0x0123456789abcdeffedcba9876543210'
	#用户还可以定义其他的一些参数,不过‘NUMBER_TRACES’,'NUMBER_SAMPLES'等参数无需声明,会自动生成
} 

#定义trs文件的param数据,param为一个字典,其中保存和能量迹一一对应的数据,如明文、密文、掩码等
param={
	'plaintext': plaintext,
	'ciphertext': ciphertext
}

#调用save_trs函数进行保存
save_trs(header,traces,param,trsfile_path)