本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介: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)都对应一个唯一的输出字节。

但这不是随便填的表!它的构造非常讲究:

  1. 先求乘法逆元 :在有限域 $ \text{GF}(2^8) $ 上计算输入字节的逆;
  2. 再做仿射变换 :对逆元结果应用一组固定的异或规则和偏移量。

这两个步骤组合起来,确保了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核?😉

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:AES(Advanced Encryption Standard)是一种广泛使用的对称加密算法,具有高安全性和执行效率,其核心操作包括AddRoundKey、SubBytes、ShiftRows和MixColumns四个步骤,并通过密钥扩展生成多轮密钥。本文介绍如何使用Verilog将AES算法转化为硬件描述语言,实现在FPGA或ASIC等硬件平台上的高效加密。项目包含完整的Verilog源码、测试平台及仿真脚本,涵盖状态机控制、S盒设计、列混合与轮密钥生成等关键模块,适用于嵌入式安全与硬件加密领域的学习与开发。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐