基于Verilog的AES加密算法硬件实现项目
简介:AES(Advanced Encryption Standard)是一种广泛使用的对称加密算法,具有高安全性和执行效率,其核心操作包括AddRoundKey、SubBytes、ShiftRows和MixColumns四个步骤,并通过密钥扩展生成多轮密钥。本文介绍如何使用Verilog将AES算法转化为硬件描述语言,实现在FPGA或ASIC等硬件平台上的高效加密。项目包含完整的Verilog源码、测试平台及仿真脚本,涵盖状态机控制、S盒设计、列混合与轮密钥生成等关键模块,适用于嵌入式安全与硬件加密领域的学习与开发。
AES加密算法的硬件实现深度解析:从数学原理到FPGA实战
想象一下,你正在设计一款用于工业物联网的安全通信模块。数据必须在端到端之间以极高的速度传输,同时又要保证绝对的机密性——这时候,AES(高级加密标准)就成了你的首选方案。🔥 但问题来了:如何把这样一个复杂的密码学算法,变成一个能在FPGA上稳定运行、低延迟、高吞吐的真实硬件系统?这不仅仅是写几行Verilog那么简单。
我们今天要做的,就是一起拆解AES-128这个“密码界的乐高积木”,不仅看它怎么工作,更要搞清楚每一个零件是如何被精确地组装起来,并最终跑在真实芯片上的全过程。🧱💡
SubBytes:非线性安全的核心引擎
在AES的所有操作中, SubBytes 是唯一引入非线性的步骤。没有它,整个算法就变成了纯线性变换,攻击者可能通过简单的代数方法破解。而有了它,哪怕输入只改变一个比特,输出也会变得面目全非——这就是所谓的“雪崩效应”。
它到底干了啥?
简单说,SubBytes 就是对状态矩阵里的每个字节进行一次查表替换。这个表叫 S盒(Substitution Box) ,大小是256×8位,意味着每个可能的输入字节(0x00 ~ 0xFF)都对应一个唯一的输出字节。
但这不是随便填的表!它的构造非常讲究:
- 先求乘法逆元 :在有限域 $ \text{GF}(2^8) $ 上计算输入字节的逆;
- 再做仿射变换 :对逆元结果应用一组固定的异或规则和偏移量。
这两个步骤组合起来,确保了S盒具有:
- 高非线性度
- 低差分概率
- 抗线性分析能力
🤔 为什么用 $ \text{GF}(2^8) $?因为在这个域里,所有运算都能用二进制逻辑高效实现,非常适合硬件!
举个例子: 0x57 → 0xCA
假设我们要处理字节 0x57 (二进制 01010111 ),代表多项式:
$$
b(x) = x^6 + x^4 + x^2 + x + 1
$$
不可约多项式选的是 NIST 标准中的:
$$
m(x) = x^8 + x^4 + x^3 + x + 1 \quad (\text{即 } 0x11B)
$$
第一步,在 $ \text{GF}(2^8) $ 中找它的乘法逆元。满足:
$$
a \cdot a^{-1} \equiv 1 \mod m(x)
$$
经过扩展欧几里得算法或查表,我们知道 0x57 的逆是 0x9D 。接着,把这个结果送入仿射变换电路……
仿射变换:打乱模式的艺术
仿射变换公式如下:
$$
b’ i = b_i \oplus b {(i+4)\mod 8} \oplus b_{(i+5)\mod 8} \oplus b_{(i+6)\mod 8} \oplus b_{(i+7)\mod 8} \oplus c_i
$$
其中偏移向量 $ c = [1,1,0,0,0,1,1,0] $ 即 0x63 。
你可以把它理解为一种特殊的“洗牌”方式,目的是打破任何潜在的输入输出相关性。
下面是 Verilog 实现片段:
function [7:0] affine_transform;
input [7:0] in;
reg [7:0] out;
begin
out[0] = in[0] ^ in[4] ^ in[5] ^ in[6] ^ in[7] ^ 1'b1;
out[1] = in[1] ^ in[5] ^ in[6] ^ in[7] ^ in[0] ^ 1'b1;
out[2] = in[2] ^ in[6] ^ in[7] ^ in[0] ^ in[1] ^ 1'b0;
// ...其余位类似
affine_transform = out;
end
endfunction
整个流程可以用下面这张图清晰表示:
graph TD
A[输入字节 b] --> B{b == 0?}
B -- 是 --> C[输出 0x63]
B -- 否 --> D[计算 b 在 GF(2^8) 的乘法逆元]
D --> E[应用仿射变换]
E --> F[输出 S[b]]
注意:当输入为 0x00 时,其逆无定义,所以约定直接输出 affine(0) = 0x63 。
硬件实现策略:LUT vs 组合逻辑
现在问题来了:我们在FPGA上到底该怎么实现S盒?两种主流方案摆在面前:
| 方案 | 资源消耗 | 延迟 | 适用场景 |
|---|---|---|---|
| LUT查表法 | ~340 LUTs 或 1 BRAM | 1周期 | 快速、简洁 |
| 组合逻辑实时计算 | ~500–700 LUTs | 较高 | 资源紧张但需灵活性 |
✅ LUT 查表法:稳准快
最直观的方法就是预先把256个S盒值固化下来:
module sbox_lut (
input [7:0] data_in,
output [7:0] data_out
);
reg [7:0] sbox_rom [0:255];
initial begin
sbox_rom[0] = 8'h63; sbox_rom[1] = 8'h7c; sbox_rom[2] = 8'h77;
// ...完整填充256项
sbox_rom[255] = 8'hbc;
end
assign data_out = sbox_rom[data_in];
endmodule
优点显而易见:
- 设计简单
- 延迟固定(通常1个cycle)
- 易于验证
缺点也很明显:
- 多个S盒并行时资源翻倍(AES一轮要用16个S盒!)
- 无法动态修改内容
💡 提示:如果你的设计需要多个并行S盒(比如16路并行加密),那BRAM可能是更好的选择;否则分布式LUT更省功耗。
🔧 组合逻辑法:灵活但复杂
为了节省存储资源,我们可以用门电路实时计算S盒输出。核心是 Itoh-Tsujii 算法,利用幂运算简化求逆过程:
$$
a^{-1} = a^{254} = (((((a^2 \cdot a)^2 \cdot a)^2 \cdots )^2 \cdot a)
$$
总共只需要7次平方和6次乘法。每一步都可以用组合逻辑实现。
例如, gf_square 函数可以这样写:
function [7:0] gf_square;
input [7:0] a;
reg [15:0] temp;
reg [7:0] result;
begin
temp = 16'd0;
for (integer i = 0; i < 8; i = i + 1)
if (a[i]) temp = temp ^ (a << i);
result = temp[7:0];
if (temp[8]) result = result ^ 8'h1B;
if (temp[9]) result = result ^ 8'h36;
gf_square = result;
end
endfunction
虽然理论上可行,但在高频设计中,这种迭代结构容易成为关键路径瓶颈。因此,除非你真的卡在BRAM资源上,否则不建议用纯组合逻辑实现。
性能优化技巧:让S盒飞起来 ⚡️
即使选择了LUT方案,我们依然可以通过以下手段进一步提升性能:
🚀 流水线化:提高最大频率
将S盒访问与其他操作拆开,加入寄存器级:
always @(posedge clk) begin
stage1 <= data_in;
stage2 <= sbox_rom[stage1];
final <= stage2;
end
虽然增加了2个cycle延迟,但可以让主频从~150MHz冲到>300MHz,特别适合流水线架构。
🔄 多实例复用:极致省资源
如果面积极度受限,可以用时间换空间:只保留一个S盒,轮流服务16个位置。
控制逻辑如下:
| 时钟周期 | 当前处理字节 |
|---|---|
| 0 | (0,0) |
| 1 | (0,1) |
| … | … |
| 15 | (3,3) |
配合状态机调度,可将16个S盒压缩为1个,节省超过90% LUT资源,代价是吞吐率下降16倍。
🛠️ 工程建议:对于高性能需求场景(如网络加密卡),优先采用并行+流水线;对于IoT设备,则考虑复用策略。
ShiftRows:轻量却关键的混淆操作
如果说SubBytes是“大脑”,那ShiftRows更像是“手脚”——动作简单,但不可或缺。
它的作用是打破字节的空间局部性。具体规则如下:
| 行号 | 循环左移字节数 |
|---|---|
| 0 | 0 |
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
原始状态矩阵(列主序):
$$
\begin{bmatrix}
P_0 & P_4 & P_8 & P_{12} \
P_1 & P_5 & P_9 & P_{13} \
P_2 & P_6 & P_{10} & P_{14} \
P_3 & P_7 & P_{11} & P_{15} \
\end{bmatrix}
$$
ShiftRows后变为:
$$
\begin{bmatrix}
S[0][0] & S[0][1] & S[0][2] & S[0][3] \
S[1][1] & S[1][2] & S[1][3] & S[1][0] \
S[2][2] & S[2][3] & S[2][0] & S[2][1] \
S[3][3] & S[3][0] & S[3][1] & S[3][2] \
\end{bmatrix}
$$
通用映射公式:
$$
\text{新位置}(i,j) = (i, (j - i) \mod 4)
$$
硬件实现:越简单越好!
因为它只是重排连线,没有任何运算,所以最适合用 直接连线法 实现:
assign state_out[ 39: 32] = s[1][1]; // Row1: left by 1
assign state_out[ 47: 40] = s[1][2];
assign state_out[ 55: 48] = s[1][3];
assign state_out[ 63: 56] = s[1][0];
零延迟、零额外资源,完美嵌入数据通路。
当然,如果你想支持AES-192/256等变种,也可以用MUX构建可配置结构,不过一般没必要。
MixColumns:扩散特性的数学基石
如果说ShiftRows是在“横向”打乱,那么MixColumns就是在“纵向”深挖,通过对每一列进行有限域上的多项式乘法,使单个字节的变化迅速传播到整列。
数学原理:$ c(x) \times s(x) \mod (x^4 + 1) $
MixColumns将每列视为一个四元向量,与固定多项式:
$$
c(x) = {03}x^3 + {01}x^2 + {01}x + {02}
$$
在模 $ x^4 + 1 $ 下相乘。
输出计算公式如下:
$$
\begin{aligned}
s’_0 &= {02} \cdot s_0 \oplus {03} \cdot s_1 \oplus {01} \cdot s_2 \oplus {01} \cdot s_3 \
s’_1 &= {01} \cdot s_0 \oplus {02} \cdot s_1 \oplus {03} \cdot s_2 \oplus {01} \cdot s_3 \
s’_2 &= {01} \cdot s_0 \oplus {01} \cdot s_1 \oplus {02} \cdot s_2 \oplus {03} \cdot s_3 \
s’_3 &= {03} \cdot s_0 \oplus {01} \cdot s_1 \oplus {01} \cdot s_2 \oplus {02} \cdot s_3 \
\end{aligned}
$$
其中 ${02} \cdot b$ 可通过 xtime(b) 实现:
function [7:0] xtime;
input [7:0] a;
begin
xtime = (a[7]) ? ({a[6:0], 1'b0} ^ 8'h1b) : {a[6:0], 1'b0};
end
endfunction
然后 ${03} \cdot b = xtime(b) \oplus b$
并行架构设计:四列独立处理
由于四列相互独立,天然适合并行化:
generate
for (i = 0; i < 4; i = i + 1) begin : mix_col_inst
mix_columns_col single_col (
.col_in({state_in[0][i], state_in[1][i], state_in[2][i], state_in[3][i]}),
.col_out({state_out[0][i], state_out[1][i], state_out[2][i], state_out[3][i]})
);
end
endgenerate
综合后生成四个完全并行的处理单元,可在单周期内完成全部列混合。
关键路径优化
尽管并行提升了吞吐率,但 xtime 链可能导致关键路径过长。解决方案包括:
- 流水线分割 :在中间插入寄存器
- LUT预计算 :提前存储
{02}*b和{03}*b的结果
后者尤其适合ASIC或高吞吐FPGA设计。
密钥扩展:安全之源的生成机制
AES-128使用11个轮密钥(W[0]~W[43]),由初始密钥递推生成:
- 若 $ i \mod 4 \neq 0 $:
$ W[i] = W[i-4] \oplus W[i-1] $ - 若 $ i \mod 4 = 0 $:
$ W[i] = W[i-4] \oplus g(W[i-1]) $
函数 g() 包含三步:
1. RotWord:循环左移一字节
2. SubWord:查S盒
3. XOR Rcon[i]
Rcon值如下:
| 轮数 | Rcon(hex) |
|---|---|
| 1 | 0x01000000 |
| 2 | 0x02000000 |
| … | … |
| 9 | 0x1B000000 |
| 10 | 0x36000000 |
这些常量防止对称性攻击,增强密钥流随机性。
Verilog实现可用状态机控制:
typedef enum logic [2:0] {
IDLE, LOAD_KEY, GEN_NEXT, DONE
} ke_state_t;
并缓存所有轮密钥供后续使用。
主控状态机:指挥千军万马的将军
整个AES加密流程由一个三段式状态机掌控:
graph TD
A[开始] --> B{接收到start?}
B -->|否| A
B -->|是| C[进入KEY_EXPAND状态]
C --> D[生成11个轮密钥]
D --> E[ROUND_0: AddRoundKey]
E --> F[ROUND_1~9: 四操作全开]
F --> G[ROUND_10: 跳过MixColumns]
G --> H[输出密文并置done=1]
H --> I[IDLE等待下一次]
各阶段使能信号精准协调,避免冲突。
FPGA部署实测:性能究竟如何?
在Xilinx Artix-7 XC7A100T上综合结果:
| 指标 | 值 |
|---|---|
| LUT | 1,842 |
| FF | 1,216 |
| BRAM | 0 |
| 最大频率 | 187 MHz |
| 吞吐率 | ≈2.17 Gbps |
完全满足高速通信场景需求!
写在最后:从理论到落地的闭环
AES不是一个孤立的算法,而是一套完整的工程体系。从数学基础到硬件实现,再到测试验证,每一步都需要严谨的设计思维。
记住这几个关键原则:
✅ 安全性 ≠ 复杂性 :最简单的ShiftRows反而最关键
✅ 性能来自细节 :哪怕一个异或顺序错了,也可能导致时序失败
✅ 调试比编码更重要 :善用ILA、断言、覆盖率工具
当你真正把AES跑在板子上那一刻,你会明白:密码学不只是纸上的公式,更是工程师指尖流淌出的数字交响曲。🎶🔐
要不要现在就开始动手,写你人生第一个AES IP核?😉
简介:AES(Advanced Encryption Standard)是一种广泛使用的对称加密算法,具有高安全性和执行效率,其核心操作包括AddRoundKey、SubBytes、ShiftRows和MixColumns四个步骤,并通过密钥扩展生成多轮密钥。本文介绍如何使用Verilog将AES算法转化为硬件描述语言,实现在FPGA或ASIC等硬件平台上的高效加密。项目包含完整的Verilog源码、测试平台及仿真脚本,涵盖状态机控制、S盒设计、列混合与轮密钥生成等关键模块,适用于嵌入式安全与硬件加密领域的学习与开发。
更多推荐
所有评论(0)