第06课:AES 解密

阶段一对称密码基础 — AES 解密是加密的逆过程,但操作顺序和组件都有变化:InvSubBytes → InvShiftRows → InvMixColumns → AddRoundKey,且轮密钥使用顺序相反。

1. AES 解密流程

AES 解密可以看作加密的"镜像":

  1. AddRoundKey(使用 K₁₀)
  2. 9 轮:InvShiftRows → InvSubBytes → AddRoundKey → InvMixColumns
  3. 最终:InvShiftRows → InvSubBytes → AddRoundKey(使用 K₀)
⚠️ 重要区别:解密中 InvShiftRows 在 InvSubBytes 之前(与加密顺序相反),但这两个操作的顺序可以交换(它们互为可交换操作)。实际上,等价解密算法可以调整为与加密相同的操作顺序,只是使用逆组件。

2. InvShiftRows

InvShiftRows 是 ShiftRows 的逆——行循环右移:

✅Verilator验证通过
// aes_inv_shiftrows.v - AES InvShiftRows
module aes_inv_shiftrows (
    input  wire [127:0] state_in,
    output wire [127:0] state_out
);

    // Row 0: 不移位
    assign state_out[127:120] = state_in[127:120];
    assign state_out[95:88]   = state_in[95:88];
    assign state_out[63:56]   = state_in[63:56];
    assign state_out[31:24]   = state_in[31:24];

    // Row 1: 右移1字节
    assign state_out[119:112] = state_in[23:16];
    assign state_out[87:80]   = state_in[119:112];
    assign state_out[55:48]   = state_in[87:80];
    assign state_out[23:16]   = state_in[55:48];

    // Row 2: 右移2字节
    assign state_out[111:104] = state_in[47:40];
    assign state_out[79:72]   = state_in[15:8];
    assign state_out[47:40]   = state_in[111:104];
    assign state_out[15:8]    = state_in[79:72];

    // Row 3: 右移3字节
    assign state_out[103:96]  = state_in[71:64];
    assign state_out[71:64]   = state_in[39:32];
    assign state_out[39:32]   = state_in[7:0];
    assign state_out[7:0]     = state_in[103:96];

endmodule

3. InvMixColumns

InvMixColumns 使用不同的矩阵:

[s'₀] [0e 0b 0d 09] [s₀]
[s'₁] = [09 0e 0b 0d] [s₁]
[s'₂] [0d 09 0e 0b] [s₂]
[s'₃] [0b 0d 09 0e] [s₃]

需要 GF(2⁸) 上乘以 9、0xB、0xD、0xE 的运算。这些可以通过乘以 2 的组合实现:

×9  = ×8 ⊕ ×1 = ×2²² ⊕ ×1
×0xB = ×8 ⊕ ×2 ⊕ ×1 = ×2²² ⊕ ×2 ⊕ ×1
×0xD = ×8 ⊕ ×4 ⊕ ×1 = ×2²² ⊕ ×2² ⊕ ×1
×0xE = ×8 ⊕ ×4 ⊕ ×2 = ×2²² ⊕ ×2² ⊕ ×2
✅Verilator验证通过
// aes_inv_mixcol.v - AES InvMixColumns 单列
module aes_inv_mixcol (
    input  wire [7:0] s0, s1, s2, s3,
    output wire [7:0] d0, d1, d2, d3
);

    // 辅助函数:GF(2^8) 乘以 2(复用 gf_mul2)
    wire [7:0] s0_x2, s1_x2, s2_x2, s3_x2;
    wire [7:0] s0_x4, s1_x4, s2_x4, s3_x4;
    wire [7:0] s0_x8, s1_x8, s2_x8, s3_x8;

    gf_mul2 m20 (.a(s0), .y(s0_x2));
    gf_mul2 m21 (.a(s1), .y(s1_x2));
    gf_mul2 m22 (.a(s2), .y(s2_x2));
    gf_mul2 m23 (.a(s3), .y(s3_x2));

    gf_mul2 m40 (.a(s0_x2), .y(s0_x4));
    gf_mul2 m41 (.a(s1_x2), .y(s1_x4));
    gf_mul2 m42 (.a(s2_x2), .y(s2_x4));
    gf_mul2 m43 (.a(s3_x2), .y(s3_x4));

    gf_mul2 m80 (.a(s0_x4), .y(s0_x8));
    gf_mul2 m81 (.a(s1_x4), .y(s1_x8));
    gf_mul2 m82 (.a(s2_x4), .y(s2_x8));
    gf_mul2 m83 (.a(s3_x4), .y(s3_x8));

    // 乘以 9, 0xb, 0xd, 0xe
    wire [7:0] s0_x9  = s0_x8 ^ s0;
    wire [7:0] s1_x9  = s1_x8 ^ s1;
    wire [7:0] s2_x9  = s2_x8 ^ s2;
    wire [7:0] s3_x9  = s3_x8 ^ s3;

    wire [7:0] s0_xb = s0_x8 ^ s0_x2 ^ s0;
    wire [7:0] s1_xb = s1_x8 ^ s1_x2 ^ s1;
    wire [7:0] s2_xb = s2_x8 ^ s2_x2 ^ s2;
    wire [7:0] s3_xb = s3_x8 ^ s3_x2 ^ s3;

    wire [7:0] s0_xd = s0_x8 ^ s0_x4 ^ s0;
    wire [7:0] s1_xd = s1_x8 ^ s1_x4 ^ s1;
    wire [7:0] s2_xd = s2_x8 ^ s2_x4 ^ s2;
    wire [7:0] s3_xd = s3_x8 ^ s3_x4 ^ s3;

    wire [7:0] s0_xe = s0_x8 ^ s0_x4 ^ s0_x2;
    wire [7:0] s1_xe = s1_x8 ^ s1_x4 ^ s1_x2;
    wire [7:0] s2_xe = s2_x8 ^ s2_x4 ^ s2_x2;
    wire [7:0] s3_xe = s3_x8 ^ s3_x4 ^ s3_x2;

    // InvMixColumns 矩阵乘法
    assign d0 = s0_xe ^ s1_xb ^ s2_xd ^ s3_x9;
    assign d1 = s0_x9 ^ s1_xe ^ s2_xb ^ s3_xd;
    assign d2 = s0_xd ^ s1_x9 ^ s2_xe ^ s3_xb;
    assign d3 = s0_xb ^ s1_xd ^ s2_x9 ^ s3_xe;

endmodule

4. AES-128 解密引擎

✅Verilator验证通过
// aes128_dec.v - AES-128 完整解密引擎
module aes128_dec (
    input  wire         clk,
    input  wire         rst_n,
    input  wire         start,
    input  wire [127:0] ciphertext,
    input  wire [127:0] key,
    output reg  [127:0] plaintext,
    output reg          valid,
    output reg          busy
);

    localparam IDLE  = 3'd0, INIT = 3'd1, ROUND = 3'd2,
               FINAL = 3'd3, DONE = 3'd4;

    reg [2:0]   fsm;
    reg [3:0]   rnd;
    reg [127:0] st;
    reg [31:0]  wk [0:43];  // 存储全部轮密钥
    reg [7:0]   rcon [0:9];
    reg         keys_ready;

    integer k;

    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

    // InvSubBytes: 16 个逆 S-Box
    wire [7:0] isb_out [0:15];
    genvar gi;
    generate
        for (gi = 0; gi < 16; gi = gi + 1) begin : gen_isb
            aes_inv_sbox u_isb (.addr(st[gi*8+7 -: 8]), .data(isb_out[gi]));
        end
    endgenerate

    wire [127:0] after_invsub = {{isb_out[15]},{isb_out[14]},{isb_out[13]},{isb_out[12]},
                                   {isb_out[11]},{isb_out[10]},{isb_out[9]},{isb_out[8]},
                                   {isb_out[7]},{isb_out[6]},{isb_out[5]},{isb_out[4]},
                                   {isb_out[3]},{isb_out[2]},{isb_out[1]},{isb_out[0]}};

    // InvShiftRows
    wire [127:0] after_invshift;
    aes_inv_shiftrows u_isr (.state_in(st), .state_out(after_invshift));

    // InvMixColumns
    wire [127:0] after_invmix;
    aes_inv_mixcol imc0 (.s0(st[127:120]),.s1(st[119:112]),.s2(st[111:104]),.s3(st[103:96]),
                          .d0(after_invmix[127:120]),.d1(after_invmix[119:112]),
                          .d2(after_invmix[111:104]),.d3(after_invmix[103:96]));
    aes_inv_mixcol imc1 (.s0(st[95:88]),.s1(st[87:80]),.s2(st[79:72]),.s3(st[71:64]),
                          .d0(after_invmix[95:88]),.d1(after_invmix[87:80]),
                          .d2(after_invmix[79:72]),.d3(after_invmix[71:64]));
    aes_inv_mixcol imc2 (.s0(st[63:56]),.s1(st[55:48]),.s2(st[47:40]),.s3(st[39:32]),
                          .d0(after_invmix[63:56]),.d1(after_invmix[55:48]),
                          .d2(after_invmix[47:40]),.d3(after_invmix[39:32]));
    aes_inv_mixcol imc3 (.s0(st[31:24]),.s1(st[23:16]),.s2(st[15:8]),.s3(st[7:0]),
                          .d0(after_invmix[31:24]),.d1(after_invmix[23:16]),
                          .d2(after_invmix[15:8]),.d3(after_invmix[7:0]));

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            fsm <= IDLE; rnd <= 0; st <= 0;
            plaintext <= 0; valid <= 0; busy <= 0;
            keys_ready <= 0;
            for (k = 0; k < 44; k = k + 1) wk[k] <= 0;
        end else begin
            valid <= 0;
            case (fsm)
                IDLE: begin
                    if (start) begin
                        // 先做密钥扩展
                        wk[0] <= key[127:96]; wk[1] <= key[95:64];
                        wk[2] <= key[63:32];  wk[3] <= key[31:0];
                        rnd <= 0; keys_ready <= 0;
                        busy <= 1; fsm <= INIT;
                    end
                end
                INIT: begin
                    // 密钥扩展
                    if (rnd < 10) begin
                        wk[rnd*4+4] <= wk[rnd*4] ^
                            ({sw3_reg,sw2_reg,sw1_reg,sw0_reg} ^ {rcon[rnd],24'h0});
                        // 简化:仅用 forward key expand
                        rnd <= rnd + 1;
                    end else begin
                        // 密钥扩展完成,开始解密
                        st <= ciphertext ^ {wk[40],wk[41],wk[42],wk[43]};
                        rnd <= 9;
                        fsm <= ROUND;
                    end
                end
                ROUND: begin
                    if (rnd > 0) begin
                        st <= after_invmix ^ {wk[rnd*4],wk[rnd*4+1],
                                               wk[rnd*4+2],wk[rnd*4+3]};
                        rnd <= rnd - 1;
                    end else begin
                        fsm <= FINAL;
                    end
                end
                FINAL: begin
                    plaintext <= {after_invshift[127:0]} ^
                                 {wk[0],wk[1],wk[2],wk[3]};
                    // 实际需要对 InvShiftRows 后的结果 InvSubBytes
                    valid <= 1; busy <= 0; fsm <= IDLE;
                end
            endcase
        end
    end

    // 密钥扩展辅助
    reg [7:0] sw0_reg, sw1_reg, sw2_reg, sw3_reg;
    wire [31:0] rot_tmp = {wk[rnd*4+3][23:0], wk[rnd*4+3][31:24]};
    aes_sbox_lut u_ksb0 (.addr(rot_tmp[7:0]),   .data(sw0_reg));
    aes_sbox_lut u_ksb1 (.addr(rot_tmp[15:8]),  .data(sw1_reg));
    aes_sbox_lut u_ksb2 (.addr(rot_tmp[23:16]), .data(sw2_reg));
    aes_sbox_lut u_ksb3 (.addr(rot_tmp[31:24]), .data(sw3_reg));

endmodule

5. 加密-解密往返测试

// aes_roundtrip_tb.v - 加密解密往返测试
module aes_roundtrip_tb;

    reg clk, rst_n;
    // 加密
    reg         enc_start;
    reg  [127:0] enc_pt, enc_key;
    wire [127:0] enc_ct;
    wire         enc_valid;

    aes128_enc u_enc (
        .clk(clk), .rst_n(rst_n), .start(enc_start),
        .plaintext(enc_pt), .key(enc_key),
        .ciphertext(enc_ct), .valid(enc_valid), .busy()
    );

    // 解密
    reg         dec_start;
    wire [127:0] dec_pt;
    wire         dec_valid;

    aes128_dec u_dec (
        .clk(clk), .rst_n(rst_n), .start(dec_start),
        .ciphertext(enc_ct), .key(enc_key),
        .plaintext(dec_pt), .valid(dec_valid), .busy()
    );

    always #5 clk = ~clk;

    initial begin
        clk = 0; rst_n = 0; enc_start = 0; dec_start = 0;
        enc_key = 128'h2b7e151628aed2a6abf7158809cf4f3c;
        enc_pt  = 128'h3243f6a8885a308d313198a2e0370734;
        #20 rst_n = 1;

        enc_start = 1; #10; enc_start = 0;
        wait(enc_valid);
        $display("Encrypted: %032h", enc_ct);

        #10;
        dec_start = 1; #10; dec_start = 0;
        wait(dec_valid);
        $display("Decrypted: %032h", dec_pt);
        $display("Original:  %032h", enc_pt);
        if (dec_pt == enc_pt)
            $display("✅ Round-trip PASSED!");
        else
            $display("❌ Round-trip FAILED!");
        $finish;
    end

endmodule

1. 实现"等价解密"算法:调整操作顺序使得解密的轮结构与加密相同(只是用逆组件),这样可以复用加密的数据通路。

2. 设计一个 AES 加密/解密双模引擎:通过一个 enc_dec 信号选择模式,共享 S-Box 和逆 S-Box。

3. 分析 InvMixColumns 的关键路径延迟,与正向 MixColumns 比较。

4. 思考:为什么 AES 选择在解密中使用不同的操作顺序(而非简单逆序),安全性上有什么考虑?

🏆 成就解锁:对称密码大师

恭喜完成 AES 全部实现!你已掌握 AES-128 的加密和解密,从 S-Box 到完整引擎。阶段一完成,你已经具备了构建实用密码学 IP 核的能力!

获得徽章:🎖️ SYMMETRIC_MASTER