阶段四侧信道防护 — 时间恒定是最基本的侧信道防护:确保密码运算的执行时间与密钥和数据无关。本课详细讲解如何在硬件中实现时间恒定。
模约减中常见模式:
// ❌ 不安全:减法是否执行取决于数据
if (result >= modulus)
result = result - modulus;
// constant_time_sub.v - 时间恒定的条件减法
module constant_time_sub #(
parameter WIDTH = 128
)(
input wire [WIDTH-1:0] value,
input wire [WIDTH-1:0] modulus,
output wire [WIDTH-1:0] result
);
// 无条件计算两种结果
wire [WIDTH-1:0] reduced = value - modulus;
// 判断是否需要约减(不使用 if)
wire need_reduce = (value >= modulus);
// 恒定时间选择:两个路径都计算,最后选择
// 使用位掩码实现,避免多路选择器的时序差异
wire [WIDTH-1:0] mask = {WIDTH{need_reduce}};
assign result = (mask & reduced) | (~mask & value);
endmodule
// constant_time_compare.v - 时间恒定的数据比较
module constant_time_compare #(
parameter WIDTH = 128
)(
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
output wire equal, // a == b
output wire not_equal // a != b
);
// 逐位异或,然后归约
wire [WIDTH-1:0] diff = a ^ b;
// 归约OR:如果任何位不同,结果非零
wire any_diff = |diff;
// 时间恒定:总是计算所有位
assign equal = ~any_diff;
assign not_equal = any_diff;
endmodule
// aes128_ct_enc.v - 时间恒定 AES-128 加密
module aes128_ct_enc (
input wire clk,
input wire rst_n,
input wire start,
input wire [127:0] plaintext,
input wire [127:0] key,
output reg [127:0] ciphertext,
output reg valid,
output reg busy
);
// 状态机:与普通 AES 相同,但确保所有路径时间恒定
localparam IDLE=3'd0, ROUND=3'd1, FINAL=3'd2, DONE=3'd3;
reg [2:0] fsm;
reg [3:0] round_cnt;
reg [127:0] state_reg;
// 密钥扩展(与普通版本相同逻辑)
reg [31:0] w [0:3];
reg [7:0] rcon [0:9];
initial begin
rcon[0]=8'h01; rcon[1]=8'h02; rcon[2]=8'h04; rcon[3]=8'h08;
rcon[4]=8'h10; rcon[5]=8'h20; rcon[6]=8'h40; rcon[7]=8'h80;
rcon[8]=8'h1b; rcon[9]=8'h36;
end
// S-Box 实例(查找表,恒定时间)
wire [7:0] sb_out [0:15];
genvar gi;
generate
for (gi = 0; gi < 16; gi = gi + 1) begin : gen_sb
aes_sbox_lut u_sb (.addr(state_reg[gi*8+7 -: 8]), .data(sb_out[gi]));
end
endgenerate
wire [127:0] after_sub = {{sb_out[15]},{sb_out[14]},{sb_out[13]},{sb_out[12]},
{sb_out[11]},{sb_out[10]},{sb_out[9]},{sb_out[8]},
{sb_out[7]},{sb_out[6]},{sb_out[5]},{sb_out[4]},
{sb_out[3]},{sb_out[2]},{sb_out[1]},{sb_out[0]}};
// ShiftRows(纯连线,恒定时间)
wire [127:0] after_shift;
aes_shiftrows u_sr (.state_in(after_sub), .state_out(after_shift));
// MixColumns(组合逻辑,恒定时间)
wire [127:0] after_mix;
aes_mixcol mc0 (.s0(after_shift[127:120]),.s1(after_shift[119:112]),
.s2(after_shift[111:104]),.s3(after_shift[103:96]),
.d0(after_mix[127:120]),.d1(after_mix[119:112]),
.d2(after_mix[111:104]),.d3(after_mix[103:96]));
aes_mixcol mc1 (.s0(after_shift[95:88]),.s1(after_shift[87:80]),
.s2(after_shift[79:72]),.s3(after_shift[71:64]),
.d0(after_mix[95:88]),.d1(after_mix[87:80]),
.d2(after_mix[79:72]),.d3(after_mix[71:64]));
aes_mixcol mc2 (.s0(after_shift[63:56]),.s1(after_shift[55:48]),
.s2(after_shift[47:40]),.s3(after_shift[39:32]),
.d0(after_mix[63:56]),.d1(after_mix[55:48]),
.d2(after_mix[47:40]),.d3(after_mix[39:32]));
aes_mixcol mc3 (.s0(after_shift[31:24]),.s1(after_shift[23:16]),
.s2(after_shift[15:8]),.s3(after_shift[7:0]),
.d0(after_mix[31:24]),.d1(after_mix[23:16]),
.d2(after_mix[15:8]),.d3(after_mix[7:0]));
// ⭐ 关键:最后一轮也计算 MixColumns,但选择时忽略
// 这确保所有轮的执行路径完全相同
wire [127:0] mix_result = after_mix ^ {w[0],w[1],w[2],w[3]};
wire [127:0] shift_result = after_shift ^ {w[0],w[1],w[2],w[3]};
// 密钥扩展辅助
wire [31:0] rot_w = {w[3][23:0], w[3][31:24]};
wire [7:0] sw0,sw1,sw2,sw3;
aes_sbox_lut u_rsb0 (.addr(rot_w[7:0]), .data(sw0));
aes_sbox_lut u_rsb1 (.addr(rot_w[15:8]), .data(sw1));
aes_sbox_lut u_rsb2 (.addr(rot_w[23:16]), .data(sw2));
aes_sbox_lut u_rsb3 (.addr(rot_w[31:24]), .data(sw3));
wire [31:0] t_func = {sw3,sw2,sw1,sw0} ^ {rcon[round_cnt-1],24'h0};
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
fsm <= IDLE; round_cnt <= 0;
state_reg <= 0; ciphertext <= 0;
valid <= 0; busy <= 0;
w[0] <= 0; w[1] <= 0; w[2] <= 0; w[3] <= 0;
end else begin
valid <= 0;
case (fsm)
IDLE: if (start) begin
w[0]<=key[127:96]; w[1]<=key[95:64];
w[2]<=key[63:32]; w[3]<=key[31:0];
state_reg <= plaintext ^ key;
round_cnt <= 1; busy <= 1;
fsm <= ROUND;
end
ROUND: begin
// 更新密钥
w[0]<=w[0]^t_func; w[1]<=w[1]^(w[0]^t_func);
w[2]<=w[2]^(w[1]^(w[0]^t_func));
w[3]<=w[3]^(w[2]^(w[1]^(w[0]^t_func)));
// ⭐ 恒定时间:总是计算 MixColumns
// 最后一轮也计算但丢弃
state_reg <= mix_result;
round_cnt <= round_cnt + 1;
if (round_cnt == 9) fsm <= FINAL;
end
FINAL: begin
// 最后一轮使用 shift_result(无 MixColumns)
w[0]<=w[0]^t_func; w[1]<=w[1]^(w[0]^t_func);
w[2]<=w[2]^(w[1]^(w[0]^t_func));
w[3]<=w[3]^(w[2]^(w[1]^(w[0]^t_func)));
state_reg <= shift_result;
fsm <= DONE;
end
DONE: begin
ciphertext <= state_reg;
valid <= 1; busy <= 0;
fsm <= IDLE;
end
endcase
end
end
endmodule
1. 用示波器测量 AES 加密的功耗轨迹,验证恒定时间版本和原始版本的时序差异。
2. 实现恒定时间的 HMAC 比较:验证时即使不匹配也要执行完整比较。
3. 分析 AES 中 S-Box 查找表的时间恒定性:FPGA BRAM 访问时间是否数据依赖?
4. 设计一个时间恒定的模逆模块:确保所有输入值的执行时间相同。
你已掌握时间恒定实现的核心技术:无条件计算、位掩码选择和消除条件分支。时间恒定是侧信道防护的第一道防线!
获得徽章:⏱️ TIME_GUARDIAN
推荐使用以下工具链进行课程实践:
# 安装 Verilator
sudo apt install verilator
# 安装 Icarus Verilog(可选)
sudo apt install iverilog
# 安装 GTKWave(波形查看器)
sudo apt install gtkwave
# 验证安装
verilator --lint-only --version
iverilog -V
密码学硬件实现的关键性能指标:
这些指标之间通常存在 trade-off,设计时需根据应用场景权衡。
本课涉及的核心概念和技术关系:
Verilog 仿真调试的常用方法:
// 调试示例
initial begin
$dumpfile("sim.vcd");
$dumpvars(0, uut);
end
// 断言验证
assert property (@(posedge clk) valid |-> data !== 'x)
else $error("Invalid data when valid!");
# 1. 语法检查
verilator --lint-only module.v
# 2. 创建 C++ 测试主函数
cat > sim_main.cpp << 'EOF'
#include "Vmodule.h"
#include "verilated.h"
int main(int argc, char** argv) {
Verilated::commandArgs(argc, argv);
Vmodule* top = new Vmodule;
top->clk = 0; top->rst_n = 0;
top->eval();
top->rst_n = 1;
for (int i = 0; i < 100; i++) {
top->clk = !top->clk;
top->eval();
}
delete top;
return 0;
}
EOF
# 3. 编译
verilator -cc module.v --exe sim_main.cpp
make -C obj_dir -f Vmodule.mk
# 4. 运行
./obj_dir/Vmodule
密码硬件的性能评估维度:
| 指标 | 单位 | 说明 |
|---|---|---|
| 面积 | GE / LUT | 等效门数或查找表数量 |
| 频率 | MHz | 最大时钟频率 |
| 吞吐量 | Gbps | 每秒处理的数据量 |
| 延迟 | 周期数 | 从输入到输出的周期 |
| 能效 | pJ/bit | 每比特能耗 |
| 面积效率 | Gbps/GE | 单位面积吞吐量 |
不同应用场景对指标优先级不同:IoT 偏重面积和能效,服务器偏重吞吐量。