AES-128在FPGA上的Verilog实现
本文详细讲解了AES-128加密算法在FPGA上的Verilog实现方法,涵盖S盒、ShiftRows、MixColumns和AddRoundKey等核心模块的设计与优化,重点分析了可综合代码结构、资源权衡及性能考量,适用于嵌入式安全与硬件加速应用。
AES128-FPGA-Verilog完整代码:技术分析与实现详解
在当今物联网设备无处不在、边缘计算节点密集部署的背景下,数据安全早已不再是软件层的附加功能,而是硬件设计中必须前置考量的核心要素。从智能门锁到工业传感器,从无线通信模块到可穿戴设备,任何涉及敏感信息传输或存储的系统都面临着被窃听、篡改甚至逆向工程的风险。在这种环境下,高级加密标准(AES)作为全球公认的对称加密基石,其硬件级实现的重要性愈发凸显。
FPGA凭借其天然的并行处理能力、灵活的可重构架构以及接近ASIC的性能表现,成为构建高效加密引擎的理想平台。尤其是AES-128——这个在安全性与资源开销之间取得精妙平衡的算法,正越来越多地以专用IP核的形式嵌入各类嵌入式系统之中。本文不打算泛泛而谈AES原理,而是聚焦于一个实际问题:如何用Verilog在FPGA上写出一套 真正可用、可综合、结构清晰且具备工程价值 的AES-128加密核心?
我们不会停留在理论描述,而是深入到每一行代码背后的权衡取舍——比如S盒到底是用ROM还是组合逻辑?密钥扩展该预计算还是实时生成?MixColumns的关键路径该如何优化?通过这些问题的探讨,我们将逐步构建出一个既适合教学理解,又能直接集成进真实项目的迭代式AES-128模块。
AES-128本质上是一个基于代换-置换网络(SPN)的分组密码,它将128位明文块在128位密钥控制下,经过10轮非线性变换,最终输出等长密文。不同于Feistel结构的DES,AES每一轮的操作都是“全连接”的:每一个输出比特都依赖于输入状态中的多个比特,这种强扩散特性正是其抗差分和线性分析能力的基础。
整个加密流程可以概括为:一次初始轮密钥加,接着9轮完整的主循环(SubBytes → ShiftRows → MixColumns → AddRoundKey),最后是第10轮省略MixColumns的简化轮。这看似简单的四步操作背后,其实蕴含着精心设计的数学原理与工程实现挑战。
先看 SubBytes ——这是整个算法中唯一的非线性环节,也是抵抗密码分析的关键所在。它的核心是一个名为S盒的查找表,大小为256字节,每个输入字节经过GF(2^8)上的乘法逆元运算后再施加仿射变换得到输出。在FPGA中,S盒通常有两种实现方式:一种是使用Block RAM构造只读存储器(ROM),另一种则是完全展开为组合逻辑。前者节省LUT但引入访问延迟,后者虽然占用更多资源(约256个LUT6),但路径更短、时序更可控。对于工作频率较高(如>100MHz)的设计,我倾向于采用组合逻辑实现,尤其是在Xilinx Artix-7这类LUT资源相对充裕的器件上。
function [7:0] sbox(input [7:0] a);
case(a)
8'h00: return 8'hc6; 8'h01: return 8'hs5; 8'h02: return 8'h54; 8'h03: return 8'h2e;
8'h04: return 8'h81; 8'h05: return 8'h83; 8'h06: return 8'hec; 8'h07: return 8'hdb;
// ... 完整填充256项 ...
default: return 8'h00;
endcase
endfunction
这里需要注意的是,虽然 case 语句写起来直观,但在综合工具眼中可能无法最优映射为LUT。更推荐的做法是在模块内部声明一个 reg [7:0] s_box[0:255] 数组,并在 initial 块中初始化,这样综合器能更好地识别为常量查找表。
接下来是 ShiftRows ,听起来复杂,实则纯粹是数据重排。状态矩阵按列优先排列成16字节的一维向量后,第一行不动,第二行左移1字节,第三行左移2,第四行左移3。由于这只是连线级别的操作,没有任何算术单元参与,因此几乎不消耗额外资源。关键在于索引顺序不能出错:
// 状态向量定义:state[0] ~ state[15] 对应列优先存储
// 即:
// [ s0 s4 s8 s12 ] => state[0], state[4], state[8], state[12]
// [ s1 s5 s9 s13 ] => state[1], state[5], state[9], state[13]
// [ s2 s6 s10 s14 ] => state[2], state[6], state[10], state[14]
// [ s3 s7 s11 s15 ] => state[3], state[7], state[11], state[15]
// ShiftRows 后的新排列
assign shifted = '{
state[0], state[5], state[10], state[15], // row0: no shift
state[4], state[9], state[14], state[3], // row1: left1
state[8], state[13], state[2], state[7], // row2: left2
state[12], state[1], state[6], state[11] // row3: left3
};
真正的性能瓶颈往往出现在 MixColumns 。这一层在GF(2^8)域上对每一列进行线性混合,使得每个输出字节都依赖于本列所有四个输入字节,极大增强了雪崩效应。其数学本质是多项式模乘,系数固定为 {03}x³ + {01}x² + {01}x + {02} 。为了高效实现,常用 xtime 函数来加速 {02}*byte 的计算:
function [7:0] xtime(input [7:0] a);
return (a[7]) ? (a << 1) ^ 8'h1b : (a << 1);
endfunction
function [31:0] mix_col(input [31:0] col);
reg [7:0] s0, s1, s2, s3;
s0 = col[31:24]; s1 = col[23:16]; s2 = col[15:8]; s3 = col[7:0];
return {
xtime(s0) ^ xtime(s1) ^ s1 ^ s2 ^ s3,
s0 ^ xtime(s1) ^ xtime(s2) ^ s2 ^ s3,
s0 ^ s1 ^ xtime(s2) ^ xtime(s3) ^ s3,
xtime(s0) ^ s0 ^ s1 ^ s2 ^ xtime(s3)
};
endfunction
注意这里的异或表达式并非随意排列,而是严格遵循FIPS 197标准中的矩阵乘法规则。此外, xtime 中的 8'h1b 是不可约多项式 x^8 + x^4 + x^3 + x + 1 的十六进制表示,这一点绝对不能错。
至于 AddRoundKey ,反而是最简单的一步:就是当前状态与轮密钥做按位异或。但别小看它——正是这一步将密钥材料注入到加密过程中,使整个变换具有密钥相关性。轮密钥来自原始密钥的扩展过程,即 Key Expansion 。
密钥扩展机制本身也是一套精密的状态机。从原始128位密钥出发,逐词生成共11个轮密钥(每轮16字节)。其中每隔4个词就要执行一次g变换:RotWord(循环左移)、SubWord(查S盒)、再与轮常数Rcon异或。Rcon的作用是打破对称性,防止密钥模式重复。在FPGA中,我们可以选择两种策略:一是启动前一次性预计算所有轮密钥并缓存;二是每轮动态生成。前者速度快但需要额外寄存器保存44字(176字节),后者节省面积但增加控制复杂度。对于资源紧张的应用,建议采用预加载方式,在 INIT 阶段完成全部扩展。
现在来看整体架构。典型的AES-128 FPGA实现采用 控制-数据分离 结构:一个有限状态机(FSM)负责调度流程,另一条数据通路执行具体运算。状态包括IDLE、INIT、SUB_BYTES、SHIFT_ROWS、MIX_COLUMNS、ADD_ROUND_KEY、FINAL_OUTPUT等。由于采用迭代结构,同一套硬件资源被复用于10轮操作,显著降低了LUT和FF的占用量(实测在Artix-7上约4800 LUTs左右)。
下面是精简后的可综合核心代码框架:
module aes128_encrypt (
input clk,
input rst_n,
input start,
input [127:0] plaintext,
input [127:0] key,
output reg [127:0] ciphertext,
output reg done
);
parameter ROUNDS = 10;
// 状态与轮密钥寄存器
reg [7:0] state [0:15];
reg [7:0] round_key [0:15];
reg [3:0] round_cnt;
// FSM状态定义
typedef enum logic [2:0] {
IDLE, INIT, ROUND_START, SUB_BYTES, SHIFT_ROWS,
MIX_COLUMNS, ADD_ROUND_KEY, FINAL_OUTPUT
} state_t;
state_t cur_state, next_state;
// S-box 和 xtime 函数(略)
// 控制状态机(组合逻辑)
always @(*) begin
next_state = cur_state;
case (cur_state)
IDLE: if (start) next_state = INIT;
INIT: next_state = ROUND_START;
ROUND_START: next_state = (round_cnt == ROUNDS) ? FINAL_OUTPUT : SUB_BYTES;
SUB_BYTES: next_state = SHIFT_ROWS;
SHIFT_ROWS: next_state = (round_cnt == ROUNDS) ? ADD_ROUND_KEY : MIX_COLUMNS;
MIX_COLUMNS: next_state = ADD_ROUND_KEY;
ADD_ROUND_KEY: next_state = ROUND_START;
FINAL_OUTPUT: next_state = IDLE;
endcase
end
// 主时序逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cur_state <= IDLE;
round_cnt <= 0;
done <= 0;
end else begin
cur_state <= next_state;
unique case (cur_state)
INIT: begin
// 加载明文(列优先)
for (int i = 0; i < 16; i++)
state[i] <= plaintext[(15-i)*8 +: 8];
// TODO: 计算第0轮密钥(原始密钥拆分)
round_cnt <= 0;
end
SUB_BYTES: begin
for (int i = 0; i < 16; i++)
state[i] <= sbox(state[i]);
end
SHIFT_ROWS: begin
state <= '{
state[0], state[5], state[10], state[15],
state[4], state[9], state[14], state[3],
state[8], state[13], state[2], state[7],
state[12], state[1], state[6], state[11]
};
end
MIX_COLUMNS: begin
for (int c = 0; c < 4; c++) begin
automatic [31:0] col = {state[c], state[c+4], state[c+8], state[c+12]};
automatic [31:0] mcol = mix_col(col);
state[c] <= mcol[31:24];
state[c+4] <= mcol[23:16];
state[c+8] <= mcol[15:8];
state[c+12] <= mcol[7:0];
end
end
ADD_ROUND_KEY: begin
// 此处应更新轮密钥(根据round_cnt生成)
for (int i = 0; i < 16; i++)
state[i] <= state[i] ^ round_key[i];
if (round_cnt < ROUNDS)
round_cnt <= round_cnt + 1;
end
FINAL_OUTPUT: begin
ciphertext <= {<<8>{state}}; // SystemVerilog位流反转语法
done <= 1;
end
default: ;
endcase
end
end
endmodule
这段代码虽然省略了完整的S盒和密钥扩展细节,但它展示了如何用SystemVerilog风格编写清晰、可综合的RTL。特别值得注意的是 {<<8>{state}} 这种语法,它能自动将小端格式的字节数组重组为大端输出,极大简化了数据打包过程。
当这个模块投入实际应用时,它可以无缝接入多种场景:例如作为SPI外设的数据加密前端,保护固件更新包;或是集成在音频SoC中,对PCM流进行实时加密封装。更重要的是,硬件实现解放了CPU负担,尤其适用于Cortex-M类低功耗MCU无法承受软件加密开销的情况。
当然,这样的基础版本也有局限。若要应对更严苛的安全环境,还需加入抗侧信道攻击措施,如随机掩码、双轨预充电逻辑等。但从工程角度看,一个稳定、正确、资源可控的基础模块才是后续增强的前提。验证环节务必使用NIST官方测试向量(如KATs)进行全面校验,确保每一个bit都符合预期。
最终我们会发现,这套AES-128设计的价值不仅在于“能用”,更在于它的 透明性与可控性 。在开源硬件日益重要的今天,掌握从算法到门级的完整实现链条,意味着我们不再依赖黑盒IP,而是真正拥有了构建自主安全体系的能力。未来可扩展的方向很多:支持CBC/GCM模式、添加解密路径、封装为AXI-Lite从设备……但所有这些演进,都始于这样一个简单却坚实的起点。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)