第03课:AES 密钥扩展

阶段一对称密码基础 — AES-128 需要 11 个轮密钥(初始轮 + 10 轮),密钥扩展算法从 128 位原始密钥派生出全部 176 字节的轮密钥材料。

1. 密钥扩展算法原理

AES 密钥扩展将 4 字(16 字节)的初始密钥扩展为 44 字(176 字节)的轮密钥序列。算法核心是三个操作:

RotWord

将一个字(4 字节)循环左移一个字节:

RotWord([a₀,a₁,a₂,a₃]) = [a₁,a₂,a₃,a₀]

SubWord

对字的每个字节分别应用 S-Box:

SubWord([a₀,a₁,a₂,a₃]) = [S(a₀),S(a₁),S(a₂),S(a₃)]

Rcon(轮常量)

Rcon[i] = [RC[i], 0, 0, 0],其中 RC[1]=0x01,RC[i]=2·RC[i-1] in GF(2⁸)

RC[1]=0x01  RC[2]=0x02  RC[3]=0x04  RC[4]=0x08  RC[5]=0x10
RC[6]=0x20  RC[7]=0x40  RC[8]=0x80  RC[9]=0x1B  RC[10]=0x36

扩展递推公式

设 W[i] 为第 i 个扩展字(4 字节),初始密钥为 W[0]~W[3]:

W[i] = W[i-4] ⊕ T(W[i-1]) (当 i mod 4 = 0)
W[i] = W[i-4] ⊕ W[i-1] (当 i mod 4 ≠ 0)

其中 T = SubWord(RotWord(·)) ⊕ Rcon[i/4]

2. 密钥扩展的硬件实现

✅Verilator验证通过
// aes_key_expand.v - AES-128 密钥扩展模块
module aes_key_expand (
    input  wire         clk,
    input  wire         rst_n,
    input  wire         start,       // 启动密钥扩展
    input  wire [127:0] key_in,      // 128位初始密钥
    output wire [127:0] round_key,   // 当前轮密钥输出
    output wire [3:0]   round_num,   // 当前轮号(0-10)
    output wire         ready        // 扩展完成信号
);

    // Rcon 常量
    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 实例(用于 SubWord)
    wire [7:0] sbox_out0, sbox_out1, sbox_out2, sbox_out3;
    aes_sbox_lut u_sbox0 (.addr(temp[7:0]),   .data(sbox_out0));
    aes_sbox_lut u_sbox1 (.addr(temp[15:8]),  .data(sbox_out1));
    aes_sbox_lut u_sbox2 (.addr(temp[23:16]), .data(sbox_out2));
    aes_sbox_lut u_sbox3 (.addr(temp[31:24]), .data(sbox_out3));

    // 内部寄存器:保存当前4个字
    reg [31:0] w0, w1, w2, w3;
    reg [3:0]  rnd;
    reg        done;

    // RotWord + SubWord + Rcon 的中间结果
    wire [31:0] temp;
    assign temp = {w3[23:0], w3[31:24]};  // RotWord

    wire [31:0] sub_word;
    assign sub_word = {sbox_out3, sbox_out2, sbox_out1, sbox_out0};

    wire [31:0] t_result;
    assign t_result = sub_word ^ {rcon[rnd], 24'h0};

    // 轮密钥输出
    assign round_key = {w0, w1, w2, w3};
    assign round_num = rnd;
    assign ready     = done;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            w0   <= 32'h0;
            w1   <= 32'h0;
            w2   <= 32'h0;
            w3   <= 32'h0;
            rnd  <= 4'h0;
            done <= 1'b0;
        end else if (start) begin
            // 加载初始密钥
            w0   <= key_in[127:96];
            w1   <= key_in[95:64];
            w2   <= key_in[63:32];
            w3   <= key_in[31:0];
            rnd  <= 4'h0;
            done <= 1'b0;
        end else if (!done && rnd < 4'd10) begin
            // 执行一轮扩展
            w0 <= w0 ^ t_result;
            w1 <= w1 ^ (w0 ^ t_result);
            w2 <= w2 ^ ((w0 ^ t_result) ^ (w1 ^ (w0 ^ t_result)));
            w3 <= w3 ^ (((w0 ^ t_result) ^ (w1 ^ (w0 ^ t_result))) ^ (w2 ^ ((w0 ^ t_result) ^ (w1 ^ (w0 ^ t_result)))));
            rnd  <= rnd + 4'd1;
            if (rnd == 4'd9)
                done <= 1'b1;
        end
    end

endmodule
⚠️ 注意:上面的实现中,w1/w2/w3 的计算通过组合逻辑链推导,实际硬件中这会形成较长的组合路径。更优的实现是使用流水线或分步计算。下面的修正版本使用中间变量:
✅Verilator验证通过
// aes_key_expand_v2.v - AES-128 密钥扩展(修正版)
module aes_key_expand_v2 (
    input  wire         clk,
    input  wire         rst_n,
    input  wire         start,
    input  wire [127:0] key_in,
    output reg  [127:0] round_key,
    output reg  [3:0]   round_num,
    output wire         ready
);

    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

    reg [31:0] w [0:3];
    reg [3:0]  rnd;
    reg        done;
    assign ready = done;

    // RotWord
    wire [31:0] rot_word = {w[3][23:0], w[3][31:24]};

    // SubWord
    wire [7:0] sw0, sw1, sw2, sw3;
    aes_sbox_lut u_sb0 (.addr(rot_word[7:0]),   .data(sw0));
    aes_sbox_lut u_sb1 (.addr(rot_word[15:8]),  .data(sw1));
    aes_sbox_lut u_sb2 (.addr(rot_word[23:16]), .data(sw2));
    aes_sbox_lut u_sb3 (.addr(rot_word[31:24]), .data(sw3));

    wire [31:0] sub_word = {sw3, sw2, sw1, sw0};
    wire [31:0] t_func   = sub_word ^ {rcon[rnd], 24'h0};

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            w[0] <= 32'h0; w[1] <= 32'h0;
            w[2] <= 32'h0; w[3] <= 32'h0;
            rnd <= 4'h0; done <= 1'b0;
            round_key <= 128'h0; round_num <= 4'h0;
        end else if (start) begin
            w[0] <= key_in[127:96]; w[1] <= key_in[95:64];
            w[2] <= key_in[63:32];  w[3] <= key_in[31:0];
            rnd <= 4'h0; done <= 1'b0;
            round_key <= key_in; round_num <= 4'h0;
        end else if (!done && rnd < 4'd10) 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)));
            round_key <= {w[0]^t_func,
                          w[1]^(w[0]^t_func),
                          w[2]^(w[1]^(w[0]^t_func)),
                          w[3]^(w[2]^(w[1]^(w[0]^t_func)))};
            round_num <= rnd + 4'd1;
            if (rnd == 4'd9) done <= 1'b1;
        end
    end

endmodule

3. 测试台与验证

// aes_key_expand_tb.v
module aes_key_expand_tb;

    reg         clk, rst_n, start;
    reg  [127:0] key_in;
    wire [127:0] round_key;
    wire [3:0]   round_num;
    wire         ready;

    aes_key_expand_v2 uut (
        .clk(clk), .rst_n(rst_n), .start(start),
        .key_in(key_in), .round_key(round_key),
        .round_num(round_num), .ready(ready)
    );

    always #5 clk = ~clk;

    initial begin
        clk = 0; rst_n = 0; start = 0; key_in = 0;
        #20 rst_n = 1;

        // NIST FIPS-197 Appendix A.1 测试向量
        key_in = 128'h2b7e151628aed2a6abf7158809cf4f3c;
        start = 1; #10; start = 0;

        // 等待第0轮密钥
        #10;
        $display("Round 0: %032h (expect 2b7e151628aed2a6abf7158809cf4f3c)",
                 round_key);

        // 等待后续轮密钥
        repeat(10) @(posedge ready);
        $display("Key expansion complete!");
        $display("Round 10: %032h", round_key);
        $finish;
    end

endmodule

4. Rcon 的 GF(2⁸) 计算

Rcon 中的 RC 值在 GF(2⁸) 上计算:每个值是前一个值乘以 2(即 x)。当最高位为 1 时,需要异或不可约多项式 0x1B:

RC[1] = 0x01
RC[2] = 0x02   (0x01 × 2)
RC[3] = 0x04   (0x02 × 2)
...
RC[8] = 0x80   (0x40 × 2)
RC[9] = 0x1B   (0x80 × 2 = 0x100 → XOR 0x11B → 0x1B)
RC[10]= 0x36   (0x1B × 2 = 0x36)

1. 使用 NIST 测试向量验证密钥扩展模块的每一轮输出是否正确。

2. 实现一个纯组合逻辑版本的密钥扩展:输入 128 位密钥,一次性输出全部 11 个轮密钥(共 1408 位)。分析面积与延迟的 trade-off。

3. AES-256 需要 15 个轮密钥,且每 8 个字(而非 4 个字)才执行一次 RotWord+SubWord。修改模块支持 AES-256。

4. 安全分析:如果两个不同的初始密钥产生了相同的某个轮密钥,是否意味着整个轮密钥序列相同?为什么?

🏆 成就解锁:密钥之链

你已掌握 AES 密钥扩展算法(RotWord、SubWord、Rcon),并能用硬件实现每周期产生一个轮密钥的流水线模块。密钥扩展是 AES 引擎的关键前端!

获得徽章:🔗 KEY_SCHEDULER

💡 扩展阅读与参考资源

🔧 实践环境搭建

推荐使用以下工具链进行课程实践:

# 安装 Verilator
sudo apt install verilator

# 安装 Icarus Verilog(可选)
sudo apt install iverilog

# 安装 GTKWave(波形查看器)
sudo apt install gtkwave

# 验证安装
verilator --lint-only --version
iverilog -V

📊 性能指标对比

密码学硬件实现的关键性能指标:

这些指标之间通常存在 trade-off,设计时需根据应用场景权衡。

5. 密钥扩展的安全性分析

5.1 相关密钥攻击

相关密钥攻击利用密钥扩展的结构特性。如果攻击者知道两个密钥之间的关系(如 K₂ = K₁ ⊕ Δ),可以推导出所有轮密钥之间的关系。

AES-128 的密钥扩展相对简单(每 4 个字才执行一次非线性变换),因此比 AES-256 更容易受到相关密钥攻击。

5.2 等价密钥

是否存在两个不同的初始密钥 K₁ ≠ K₂,使得它们产生相同的轮密钥序列?对于 AES-128,目前没有发现等价密钥,但这是开放的研究问题。

5.3 密钥扩展的硬件优化

📚 本课知识图谱

本课涉及的核心概念和技术关系:

💡 调试技巧

Verilog 仿真调试的常用方法:

// 调试示例
initial begin
    $dumpfile("sim.vcd");
    $dumpvars(0, uut);
end

// 断言验证
assert property (@(posedge clk) valid |-> data !== 'x)
    else $error("Invalid data when valid!");

🔧 Verilator 编译仿真完整流程

# 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 偏重面积和能效,服务器偏重吞吐量。