——CW305目标板的波形采集
一、目标板介绍
CW305 是一款独立的 FPGA 目标板,搭载的FPGA芯片为Xilinx Artix-7系列。 它具有与 FPGA 通信的 USB 接口、为 FPGA 提供时钟的外部 PLL、编程 VCC-INT 电源以及用于故障注入环境的二极管保护。 CW305 电路板有多种配置,与其他 ChipWhisperer 独立目标一样,它需要外部设备进行侧通道功率分析或故障注入,并具有标准 ChipWhisperer 20 引脚/SMA 接口。
Xilinx Artix-7系列是赛灵思公司推出的一款中高端现场可编程门阵列(FPGA),它主要面向需要较高性能和较低成本的设计应用。以下是Xilinx Artix-7系列的一些主要特点:
- 逻辑资源丰富:Artix-7系列提供了大量的逻辑单元、查找表(LUTs)、寄存器、块RAM和数字信号处理(DSP)切片,可以满足复杂算法和数据处理的需求。
- 高速IO:支持高速串行IO标准,如6.25 Gbps的GTP收发器,能够实现高速数据传输。
- 集成硬核处理器:部分Artix-7器件集成了硬核处理器,如PowerPC 440,可用于处理复杂控制任务。
- 高级时钟管理:具有多个数字时钟管理器(DCMs)和锁相环(PLLs),可以提供稳定的时钟信号,支持多种时钟域的操作。
二、目标板代码编写
CW305目标板的代码为verilog代码,通过vivado软件进行综合、实现和生成比特流文件。CW305目标板通过USB接口与FPGA芯片通信,目标板代码同样分为USB接口通信和密码算法实现两部分,密码算法实现本文不再讲解,读者自行学习。ChiWhisperer官网已经给出了AES循环迭代实现、AES流水线实现和ECC的代码,以及与CW305、CW312T-A35、CW312T-iCE40和CW308T-S6LX9共4种FPGA目标板的通信接口(参考https://github.com/newaetech/chipwhisperer/tree/develop/firmware/fpgas)。以CW305的AES循环迭代实现为例,涉及CW305的USB通信接口的代码包含以下6个verilog文件:
- cdc_pulse.v:不同时钟域的同步。
- clocks.v:选择输入时钟并驱动输出时钟。
- cw305_usb_reg_fe.v:处理USB接口的前端逻辑。
- cw305_defines.v:定义了一些寄存器地址(常量),用于在系统中进行寄存器访问。
- cw305_reg_aes.v:处理与AES加密相关的寄存器操作。
- cw305_top.v:顶层模块,连接了各个子模块并处理系统的整体逻辑。
前三个文件相对固定,一般无需修改,本文主要对后面三个文件进行说明。
1. cw305_defines.v
该文件定义了一些寄存器的地址,如果用户有新的需求可以添加更多的地址,默认14位的地址空间,比较充足。
`define REG_CLKSETTINGS 'h00 //密码运算单元的时钟来源
`define REG_USER_LED 'h01 //CW305目标板上的用户LED灯
`define REG_CRYPT_TYPE 'h02 //加密类型
`define REG_CRYPT_REV 'h03 //加密版本
`define REG_IDENTIFY 'h04 //标识符
`define REG_CRYPT_GO 'h05 //写该地址开始执行加/解密和采波,读该地址获取加密运算单元是否繁忙
`define REG_CRYPT_TEXTIN 'h06 //输出到加密运算单元的明文
`define REG_CRYPT_CIPHERIN 'h07 //输出到加密运算单元的密文
`define REG_CRYPT_TEXTOUT 'h08 //来自加密运算单元的明文
`define REG_CRYPT_CIPHEROUT 'h09 //来自加密运算单元的明文
`define REG_CRYPT_KEY 'h0a //密钥
`define REG_BUILDTIME 'h0b //构建时间
2. cw305_reg_aes.v
`default_nettype none //设置没有显示声明的网络类型为none,编译器会报错,用于捕获未声明的信号
`timescale 1ns / 1ps //设置仿真时间单位为1ns,精度为1ps
`include "cw305_defines.v" //包含cw305_defines.v文件//模块声明和参数化
module cw305_reg_aes #(parameter pADDR_WIDTH = 21, //寄存器地址宽度parameter pBYTECNT_SIZE = 7, //对寄存器进行字节计数的大小parameter pDONE_EDGE_SENSITIVE = 1, //指示I_done信号是否是边沿敏感的parameter pPT_WIDTH = 128, //明文宽度parameter pCT_WIDTH = 128, //密文宽度parameter pKEY_WIDTH = 128, //密钥宽度parameter pCRYPT_TYPE = 2, //加密类型parameter pCRYPT_REV = 4, //加密版本parameter pIDENTIFY = 8'h2e //标识符
)(//主机与寄存器交互的端口声明input wire usb_clk, //USB时钟input wire crypto_clk, //加密时钟input wire reset_i, //复位信号input wire [pADDR_WIDTH-pBYTECNT_SIZE-1:0] reg_address, //寄存器地址input wire [pBYTECNT_SIZE-1:0] reg_bytecnt, //寄存器中第几个字节output reg [7:0] read_data, //从寄存器读取的数据,将会通过USB接口传回主机input wire [7:0] write_data, //从主机传入的用于写入寄存器的数据input wire reg_read, //读有效标志input wire reg_write, //写有效标志input wire reg_addrvalid, //地址有效标志input wire exttrigger_in, //外部触发信号,为高则开始加密和采波//来自加密运算单元的输入信号input wire [pPT_WIDTH-1:0] I_textout, //明文(执行解密时有效)input wire [pCT_WIDTH-1:0] I_cipherout, //密文(执行加密时有效)input wire I_ready, //准备就绪input wire I_done, //执行完毕input wire I_busy, //执行中//输出到加密运算单元的信号output reg [4:0] O_clksettings, //用于选择加密运算单元的时钟信号output reg O_user_led, //用户LED灯信号output wire [pKEY_WIDTH-1:0] O_key, //密钥output wire [pPT_WIDTH-1:0] O_textin, //明文(执行加密时有效)output wire [pCT_WIDTH-1:0] O_cipherin, //密文(执行解密时有效)output wire O_start //开始执行加/解密并采波);//内部信号声明reg [7:0] reg_read_data; //存储从USB接口读取的数据reg [pCT_WIDTH-1:0] reg_crypt_cipherin; //输出到加密运算单元的密文reg [pKEY_WIDTH-1:0] reg_crypt_key; //密钥reg [pPT_WIDTH-1:0] reg_crypt_textin; //输出到加密运算单元的明文reg [pPT_WIDTH-1:0] reg_crypt_textout; //来自加密运算单元的明文reg [pCT_WIDTH-1:0] reg_crypt_cipherout; //来自加密运算单元的密文reg reg_crypt_go_pulse; //用于生成加密启动脉冲wire reg_crypt_go_pulse_crypt;//加密启动脉冲1//状态管理和信号同步reg busy_usb;reg done_r;wire done_pulse;wire crypt_go_pulse; //加密启动脉冲2reg go_r;reg go;wire [31:0] buildtime; //保存构建时间//ASYNC_REG属性用于跨时钟域的信号同步(* ASYNC_REG = "TRUE" *) reg [pKEY_WIDTH-1:0] reg_crypt_key_crypt;(* ASYNC_REG = "TRUE" *) reg [pPT_WIDTH-1:0] reg_crypt_textin_crypt;(* ASYNC_REG = "TRUE" *) reg [pPT_WIDTH-1:0] reg_crypt_textout_usb;(* ASYNC_REG = "TRUE" *) reg [pCT_WIDTH-1:0] reg_crypt_cipherout_usb;(* ASYNC_REG = "TRUE" *) reg [1:0] go_pipe;(* ASYNC_REG = "TRUE" *) reg [1:0] busy_pipe;//时钟域同步逻辑//在crypto_clk时钟域中,处理I_done信号的边沿检测,并生成done_pulse信号always @(posedge crypto_clk) begindone_r <= I_done & pDONE_EDGE_SENSITIVE;endassign done_pulse = I_done & ~done_r;//在crypto_clk时钟域中,将加密完成时的密文和明文数据存储到reg_crypt_cipherout和reg_crypt_textout中always @(posedge crypto_clk) beginif (done_pulse) beginreg_crypt_cipherout <= I_cipherout;reg_crypt_textout <= I_textout;endreg_crypt_key_crypt <= reg_crypt_key;reg_crypt_textin_crypt <= reg_crypt_textin;end//在usb_clk时钟域中,将reg_crypt_cipherout和reg_crypt_textout同步到usb_clk时钟域always @(posedge usb_clk) beginreg_crypt_cipherout_usb <= reg_crypt_cipherout;reg_crypt_textout_usb <= reg_crypt_textout;end//将同步后的信号作为密码运算单元的输入assign O_textin = reg_crypt_textin_crypt;assign O_key = reg_crypt_key_crypt;assign O_start = crypt_go_pulse || reg_crypt_go_pulse_crypt;//读寄存器逻辑,读取结果会通过USB接口传回电脑主机always @(*) beginif (reg_addrvalid && reg_read) begin //当reg_addrvalid和reg_read为高电平时开始读寄存器case (reg_address) //根据reg_address选择读取的数据是什么`REG_CLKSETTINGS: reg_read_data = O_clksettings; //读加密时钟设置寄存器`REG_USER_LED: reg_read_data = O_user_led; //读用户LED灯寄存器`REG_CRYPT_TYPE: reg_read_data = pCRYPT_TYPE; //读加密类型`REG_CRYPT_REV: reg_read_data = pCRYPT_REV; //读加密`REG_IDENTIFY: reg_read_data = pIDENTIFY; //读标识符`REG_CRYPT_GO: reg_read_data = busy_usb; //读加密运算单元是否繁忙//reg_bytecnt在此处指明读取128位数据中的哪个字节//读密钥寄存器`REG_CRYPT_KEY: reg_read_data = reg_crypt_key[reg_bytecnt*8 +: 8];//读输出到加密运算单元的明文寄存器`REG_CRYPT_TEXTIN: reg_read_data = reg_crypt_textin[reg_bytecnt*8 +: 8];//读输出到加密运算单元的密文寄存器`REG_CRYPT_CIPHERIN: reg_read_data = reg_crypt_cipherin[reg_bytecnt*8 +: 8];//读来自加密运算单元的明文寄存器`REG_CRYPT_TEXTOUT: reg_read_data = reg_crypt_textout_usb[reg_bytecnt*8 +: 8];//读来自加密运算单元的密文寄存器`REG_CRYPT_CIPHEROUT: reg_read_data = reg_crypt_cipherout_usb[reg_bytecnt*8 +: 8];//读构建时间寄存器`REG_BUILDTIME: reg_read_data = buildtime[reg_bytecnt*8 +: 8];default: reg_read_data = 0;endcaseendelsereg_read_data = 0;end//在usb_clk时钟域中,将reg_read_data赋值给read_dataalways @(posedge usb_clk)read_data <= reg_read_data;//写寄存器逻辑,将来自电脑主机的数据写入寄存器always @(posedge usb_clk) beginif (reset_i) beginO_clksettings <= 0;O_user_led <= 0;reg_crypt_go_pulse <= 1'b0;endelse beginif (reg_addrvalid && reg_write) begin //当reg_addrvalid和reg_write为高电平时开始写寄存器case (reg_address)`REG_CLKSETTINGS: O_clksettings <= write_data; //写加密时钟设置寄存器`REG_USER_LED: O_user_led <= write_data; //写用户LED灯寄存器//写输出到加密运算单元的明文寄存器`REG_CRYPT_TEXTIN: reg_crypt_textin[reg_bytecnt*8 +: 8] <= write_data;//写输出到加密运算单元的密文寄存器`REG_CRYPT_CIPHERIN: reg_crypt_cipherin[reg_bytecnt*8 +: 8] <= write_data;//写密钥寄存器`REG_CRYPT_KEY: reg_crypt_key[reg_bytecnt*8 +: 8] <= write_data;endcaseend//写入REG_CRYPT_GO寄存器会生成一个脉冲信号,用于启动加/解密和采波过程if ( (reg_addrvalid && reg_write && (reg_address == `REG_CRYPT_GO)) )reg_crypt_go_pulse <= 1'b1;elsereg_crypt_go_pulse <= 1'b0;endend//跨时钟域同步逻辑//在crypto_clk时钟域中,处理exttrigger_in信号的同步,并生成crypt_go_pulse信号always @(posedge crypto_clk) begin{go_r, go, go_pipe} <= {go, go_pipe, exttrigger_in};endassign crypt_go_pulse = go & !go_r;//使用cdc_pulse模块将reg_crypt_go_pulse信号从usb_clk时钟域同步到crypto_clk时钟域cdc_pulse U_go_pulse (.reset_i (reset_i),.src_clk (usb_clk),.src_pulse (reg_crypt_go_pulse),.dst_clk (crypto_clk),.dst_pulse (reg_crypt_go_pulse_crypt));//在usb_clk时钟域中,将I_busy信号同步到busy_usbalways @(posedge usb_clk){busy_usb, busy_pipe} <= {busy_pipe, I_busy};//如果没有定义__ICARUS__宏,则使用USR_ACCESSE2原语读取构建时间`ifndef __ICARUS__USR_ACCESSE2 U_buildtime (.CFGCLK(),.DATA(buildtime),.DATAVALID());`else //如果定义了__ICARUS__宏,则将buildtime信号赋值为0assign buildtime = 0;`endifendmodule`default_nettype wire //恢复默认的网络类型为wire
如果密码运算单元还需要输入其他数据,则需要新定义一个寄存器,添加写该寄存器的逻辑,并将寄存器值经过同步后输出。如果密码运算单元会产生更多的输出,同样先新定义一个寄存器,在crypt_clk时钟域获取密码运算单元的输出,之后将寄存器的值同步到usb_clk时钟域,并添加读该寄存器的逻辑。最后,不要忘记在cw305_defines.v中添加寄存器地址的常量。
3. cw305_top.v
`timescale 1ns / 1ps //设置仿真时间单位为1ns,精度为1ps
`default_nettype none //设置没有显示声明的网络类型为none,编译器会报错,用于捕获未声明的信号//模块声明和参数化
module cw305_top #(parameter pBYTECNT_SIZE = 7, //对寄存器进行字节计数的大小parameter pADDR_WIDTH = 21, //寄存器地址的宽度parameter pPT_WIDTH = 128, //明文宽度parameter pCT_WIDTH = 128, //密文宽度parameter pKEY_WIDTH = 128 //密钥宽度
)(//USB接口input wire usb_clk, //USB时钟inout wire [7:0] usb_data, //USB数据,用于读写操作input wire [pADDR_WIDTH-1:0] usb_addr, //USB地址input wire usb_rdn, //USB读信号input wire usb_wrn, //USB写信号input wire usb_cen, //USB片选信号input wire usb_trigger, //触发信号//CW305目标板上DIP开关、按钮和LED灯input wire j16_sel, //DIP开关输入input wire k16_sel, //DIP开关输入input wire k15_sel, //DIP开关输入input wire l14_sel, //DIP开关输入input wire pushbutton, //复位按钮output wire led1, //红色LED输出output wire led2, //绿色LED输出output wire led3, //蓝色LED输出//PLLinput wire pll_clk1, //PLL时钟通道1//input wire pll_clk2, //PLL时钟通道2(本例中未使用)//20针连接器信号output wire tio_trigger, //输出到外部的触发信号(本例为密码运算单元繁忙信号)output wire tio_clkout, //输出到外部的密码运算单元时钟input wire tio_clkin //外部时钟);wire [pKEY_WIDTH-1:0] crypt_key; //密钥wire [pPT_WIDTH-1:0] crypt_textout; //加密运算单元的输入明文wire [pCT_WIDTH-1:0] crypt_cipherin; //加密运算单元的输出密文//加密状态信号wire crypt_ready;wire crypt_start;wire crypt_done;wire crypt_busy;//USB和寄存器相关的信号wire usb_clk_buf;wire [7:0] usb_dout;wire isout;wire [pADDR_WIDTH-pBYTECNT_SIZE-1:0] reg_address;wire [pBYTECNT_SIZE-1:0] reg_bytecnt;wire reg_addrvalid;wire [7:0] write_data;wire [7:0] read_data;wire reg_read;wire reg_write;wire [4:0] clk_settings;wire crypt_clk; //复位信号wire resetn = pushbutton;wire reset = !resetn;//生成USB时钟的心跳信号(通过观察CW305目标板的红色LED灯是否闪烁来判断USB时钟是否正常)reg [24:0] usb_timer_heartbeat;always @(posedge usb_clk_buf) usb_timer_heartbeat <= usb_timer_heartbeat + 25'd1;assign led1 = usb_timer_heartbeat[24];//生成加密时钟的心跳信号(通过观察CW305目标板的绿色LED灯是否闪烁来判断加密时钟是否正常)reg [22:0] crypt_clk_heartbeat;always @(posedge crypt_clk) crypt_clk_heartbeat <= crypt_clk_heartbeat + 23'd1;assign led2 = crypt_clk_heartbeat[22];//实例化cw305_usb_reg_fe模块,处理USB接口的寄存器读写操作cw305_usb_reg_fe #(.pBYTECNT_SIZE (pBYTECNT_SIZE),.pADDR_WIDTH (pADDR_WIDTH)) U_usb_reg_fe (.rst (reset),.usb_clk (usb_clk_buf), .usb_din (usb_data), .usb_dout (usb_dout), .usb_rdn (usb_rdn), .usb_wrn (usb_wrn),.usb_cen (usb_cen),.usb_alen (1'b0), // unused.usb_addr (usb_addr),.usb_isout (isout), .reg_address (reg_address), .reg_bytecnt (reg_bytecnt), .reg_datao (write_data), .reg_datai (read_data),.reg_read (reg_read), .reg_write (reg_write), .reg_addrvalid (reg_addrvalid));//AES寄存器模块实例化,处理与AES加密相关的寄存器操作cw305_reg_aes #(.pBYTECNT_SIZE (pBYTECNT_SIZE),.pADDR_WIDTH (pADDR_WIDTH),.pPT_WIDTH (pPT_WIDTH),.pCT_WIDTH (pCT_WIDTH),.pKEY_WIDTH (pKEY_WIDTH)) U_reg_aes (.reset_i (reset),.crypto_clk (crypt_clk),.usb_clk (usb_clk_buf), .reg_address (reg_address[pADDR_WIDTH-pBYTECNT_SIZE-1:0]), .reg_bytecnt (reg_bytecnt), .read_data (read_data), .write_data (write_data),.reg_read (reg_read), .reg_write (reg_write), .reg_addrvalid (reg_addrvalid),.exttrigger_in (usb_trigger),.I_textout (128'b0),.I_cipherout (crypt_cipherin),.I_ready (crypt_ready),.I_done (crypt_done),.I_busy (crypt_busy),.O_clksettings (clk_settings),.O_user_led (led3),.O_key (crypt_key),.O_textin (crypt_textout),.O_cipherin (),.O_start (crypt_start));//根据isout信号的值,控制usb_data总线的方向assign usb_data = isout? usb_dout : 8'bZ;//实例化clocks模块,处理时钟选择和输出clocks U_clocks (.usb_clk (usb_clk),.usb_clk_buf (usb_clk_buf),.I_j16_sel (j16_sel),.I_k16_sel (k16_sel),.I_clock_reg (clk_settings),.I_cw_clkin (tio_clkin),.I_pll_clk1 (pll_clk1),.O_cw_clkout (tio_clkout),.O_cryptoclk (crypt_clk));//AES运算单元输入输出信号定义wire aes_clk;wire [127:0] aes_key;wire [127:0] aes_pt;wire [127:0] aes_ct;wire aes_load;wire aes_busy;assign aes_clk = crypt_clk;assign aes_key = crypt_key;assign aes_pt = crypt_textout;assign crypt_cipherin = aes_ct;assign aes_load = crypt_start;assign crypt_ready = 1'b1;assign crypt_done = ~aes_busy;assign crypt_busy = aes_busy;//AES运算单元实例化aes_core (.clk(aes_clk),.load(aes_load),.mkey(aes_key),.pt(aes_pt),.ct(aes_ct),.busy(aes_busy));//将aes_busy信号连接到tio_trigger输出端口assign tio_trigger = aes_busy;endmodule`default_nettype wire //恢复默认的网络类型为wire
可见,这三个文件是相互配合的,编写分组密码算法时,不需要改动太多,只需要修改顶层模块,以及明密文的位宽参数。当编写更复杂的算法时,需要重新编写这三个文件,不过ChiWhipserer官方已经给出了AES流水线实现的ECC实现示例,用户可在此基础上进行修改。
三、采集代码编写
采集代码为运行在jupyter notebook上的代码,ChipWhipserer官方已经给了非常详细的说明,下载网址为https://github.com/newaetech/chipwhisperer-jupyter。这里给一个比较标准的CW305目标板采集模板,并进行补充说明。
#连接捕获板,并进行一些初始设置(比较固定,一般不变)
import chipwhisperer as cw
scope = cw.scope()
scope.adc.basic_mode = "rising_edge" #上升沿触发
scope.trigger.triggers = "tio4" #触发信号为目标板20针SMA接口的IO4
scope.io.tio1 = "serial_rx" #目标板20针SMA接口的IO1用于UART接收
scope.io.tio2 = "serial_tx" #目标板20针SMA接口的IO2用于UART发送
scope.io.hs2 = "disabled" #目标板20针SMA接口的HS2置为高阻态
#本文采用的是CW305-35t的目标板
TARGET_PLATFORM = 'CW305_35t'
fpga_id = '35t'#比特流文件的路径
bit_file=r"D:\mycode\vivado\aes_normal\bitfile\cw305_top.bit"#连接目标板,并将比特流文件烧写到FPGA
target = cw.target(scope, cw.targets.CW305, force=True, fpga_id=fpga_id,bsfile=bit_file)
#设置VCC-INT电压为1V,VCC-INT是内部逻辑电路的供电电压
target.vccint_set(1.0)
#仅启用PLL1,作为密码运行执行单元的时钟信号
target.pll.pll_enable_set(True)
target.pll.pll_outenable_set(False, 0)
target.pll.pll_outenable_set(True, 1)
target.pll.pll_outenable_set(False, 2)
#设置PLL1的频率为10MHz(可设置为5MHz-160MHz)
target.pll.pll_outfreq_set(10E6, 1)
#设置在加密运行单元执行过程中自动关闭USB时钟,以减少噪声
target.clkusbautooff = True
#设置USB时钟在任务完成后1毫秒进入睡眠模式
target.clksleeptime = 1#确保ADC(模数转换器)的时钟源来自FPGA而不是内部时钟,可以提高数据采集的精度和稳定性
if scope._is_husky:scope.clock.clkgen_src = 'extclk'
else:scope.clock.adc_src = "extclk_x4"#确保DCM(数字时钟管理器)锁定
import time
for i in range(5):scope.clock.reset_adc()time.sleep(0.5)if scope.clock.adc_locked:break
assert (scope.clock.adc_locked), "ADC failed to lock"
#这里可以对一些捕获板的参数进行设置
scope.clock.adc_mul=10 #每个时钟周期采集的点数,默认为4
scope.gain.db=20 #低噪声放大器的增益,默认为25,当波形失真时可以减小该值,一般在[0,25]之间调整
scope.adc.samples = 1000 #采样点数
scope.adc.offset =0 #采样位置的偏移
'''
#波形的采集1
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
plaintext=bytearray([0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10])
key=bytearray([0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10])#官方教程中均采用类似于下面的封装函数capture_trace进行采波
trs = cw.capture_trace(scope, target, plaintext, key)
print("plaintext:",trs.textin.hex())
print("key:",trs.key.hex())
print("ciphertext:",trs.textout.hex())
plt.plot(np.array(trs.wave))
plt.show()
'''
#波形的采集2
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
plaintext=bytearray([0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10])
key=bytearray([0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10])#本文建设使用更底层的函数进行采集,以便理解如何对其他类型密码算法进行采波
#首先根据cw305_defines.v文件对target类中的寄存器地址进行设置(用户可根据实际情况设置更多的地址)
target.REG_CRYPT_KEY = 0x0a
target.REG_CRYPT_TEXTIN = 0x06
target.REG_CRYPT_GO = 0x05
target.REG_USER_LED = 0x01
target.REG_CRYPT_CIPHEROUT = 0x09
target.REG_BUILDTIME = 0x0b
target.REG_CRYPT_TYPE = 0x02
target.REG_CRYPT_REV = 0x03#写入密钥,注意写入数据均需要先逆序
key = key[::-1] #逆序操作
target.fpga_write(target.REG_CRYPT_KEY, key)#写入明文
plaintext = plaintext[::-1]
target.fpga_write(target.REG_CRYPT_TEXTIN, plaintext)#开始采波
scope.arm()
target.go()
ret = scope.capture()
if ret:print("Failed capture")
wave = scope.get_last_trace()#读取密文
ciphertext = target.fpga_read(target.REG_CRYPT_CIPHEROUT, 16)
ciphertext = ciphertext[::-1]#输出结果
print(ciphertext.hex())
plt.plot(np.array(wave))
plt.show()
从上面的程序可以看出,软件与硬件部分最关键的联系便是寄存器地址,这对于理解整个工作流程,以及扩展自己的密码算法比较重要。如果扩展密码算法比较复杂,可以参考ECC和AES流水线实现的示例,编写一个单独的采集脚本。
#开始正式采波from tqdm.notebook import trange
import numpy as np#可以根据上面的观察结果重新调整采集的点数和偏移
scope.adc.samples = 2000
scope.adc.offset =0#根据cw305_defines.v文件对target类中的寄存器地址进行设置
target.REG_CRYPT_KEY = 0x0a
target.REG_CRYPT_TEXTIN = 0x06
target.REG_CRYPT_GO = 0x05
target.REG_USER_LED = 0x01
target.REG_CRYPT_CIPHEROUT = 0x09
target.REG_BUILDTIME = 0x0b
target.REG_CRYPT_TYPE = 0x02
target.REG_CRYPT_REV = 0x03#采集60000条曲线
N=60000
plaintext=[]
ciphertext=[]
traces=[]#写入密钥,注意写入数据均需要先逆序
key=bytearray([0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10])
key = key[::-1] #逆序操作
target.fpga_write(target.REG_CRYPT_KEY, key)#开始采集
for i in trange(N, desc='Capturing traces'):plain=np.random.randint(256,size=16,dtype=np.uint8) #生成一个16字节随机明文plaintext.append(plain)plain= plain[::-1]target.fpga_write(target.REG_CRYPT_TEXTIN, plain)scope.arm()target.go()ret = scope.capture()if ret:print("Failed capture")wave = scope.get_last_trace()traces.append(wave)cipher= target.fpga_read(target.REG_CRYPT_CIPHEROUT, 16)cipher = cipher[::-1]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 = {},0for 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_sizeheader[Header.LENGTH_DATA] = param_offsetheader[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]=0with 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_classtrs_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/cw305_aes_normal.trs"#创建trs文件的头,里面可以定义一些有用的信息,具体可通过执行help(Header)查看
header={#这里定义了其中的'DESCRIPTION'参数,指明了采集目标,以及主密钥'DESCRIPTION':'target: cw305, 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)