第07课:SHA-256 压缩函数

阶段二哈希与认证 — SHA-256 是最广泛使用的密码学哈希函数,其核心是压缩函数。本课从压缩函数入手,理解 SHA-256 的数学原理和硬件映射。

1. SHA-256 概述

SHA-256 将任意长度的消息映射为 256 位(32 字节)的摘要,满足:

2. SHA-256 压缩函数结构

压缩函数接受 256 位哈希状态和 512 位消息块,输出新的 256 位状态:

Hᵢ = Compress(Hᵢ₋₁, Mᵢ)

内部有 8 个 32 位工作寄存器 a, b, c, d, e, f, g, h,迭代 64 轮:

2.1 消息调度(Message Schedule)

将 16 个 32 位消息字扩展为 64 个字:

W[t] = σ₁(W[t-2]) + W[t-7] + σ₀(W[t-15]) + W[t-16]

其中:

σ₀(x) = ROTR(x,7) ⊕ ROTR(x,18) ⊕ SHR(x,3)
σ₁(x) = ROTR(x,17) ⊕ ROTR(x,19) ⊕ SHR(x,10)

2.2 压缩轮函数

T₁ = h + Σ₁(e) + Ch(e,f,g) + K[t] + W[t]
T₂ = Σ₀(a) + Maj(a,b,c)

其中:

Ch(x,y,z) = (x ∧ y) ⊕ (¬x ∧ z)
Maj(x,y,z) = (x ∧ y) ⊕ (x ∧ z) ⊕ (y ∧ z)
Σ₀(x) = ROTR(x,2) ⊕ ROTR(x,13) ⊕ ROTR(x,22)
Σ₁(x) = ROTR(x,6) ⊕ ROTR(x,11) ⊕ ROTR(x,25)

3. SHA-256 压缩函数硬件实现

✅Verilator验证通过
// sha256_compress.v - SHA-256 压缩函数核心
module sha256_compress (
    input  wire         clk,
    input  wire         rst_n,
    input  wire         start,
    input  wire [255:0] hash_in,      // 当前哈希状态
    input  wire [511:0] block_in,     // 512位消息块
    output reg  [255:0] hash_out,     // 新的哈希状态
    output reg          valid
);

    // SHA-256 初始哈希常量
    localparam [31:0] H0 = 32'h6a09e667, H1 = 32'hbb67ae85;
    localparam [31:0] H2 = 32'h3c6ef372, H3 = 32'ha54ff53a;
    localparam [31:0] H4 = 32'h510e527f, H5 = 32'h9b05688c;
    localparam [31:0] H6 = 32'h1f83d9ab, H7 = 32'h5be0cd19;

    // 64 个轮常量 K[0..63]
    reg [31:0] K [0:63];
    initial begin
        K[0]=32'h428a2f98; K[1]=32'h71374491; K[2]=32'hb5c0fbcf; K[3]=32'he9b5dba5;
        K[4]=32'h3956c25b; K[5]=32'h59f111f1; K[6]=32'h923f82a4; K[7]=32'hab1c5ed5;
        K[8]=32'hd807aa98; K[9]=32'h12835b01; K[10]=32'h243185be; K[11]=32'h550c7dc3;
        K[12]=32'h72be5d74; K[13]=32'h80deb1fe; K[14]=32'h9bdc06a7; K[15]=32'hc19bf174;
        K[16]=32'he49b69c1; K[17]=32'hefbe4786; K[18]=32'h0fc19dc6; K[19]=32'h240ca1cc;
        K[20]=32'h2de92c6f; K[21]=32'h4a7484aa; K[22]=32'h5cb0a9dc; K[23]=32'h76f988da;
        K[24]=32'h983e5152; K[25]=32'ha831c66d; K[26]=32'hb00327c8; K[27]=32'hbf597fc7;
        K[28]=32'hc6e00bf3; K[29]=32'hd5a79147; K[30]=32'h06ca6351; K[31]=32'h14292967;
        K[32]=32'h27b70a85; K[33]=32'h2e1b2138; K[34]=32'h4d2c6dfc; K[35]=32'h53380d13;
        K[36]=32'h650a7354; K[37]=32'h766a0abb; K[38]=32'h81c2c92e; K[39]=32'h92722c85;
        K[40]=32'ha2bfe8a1; K[41]=32'ha81a664b; K[42]=32'hc24b8b70; K[43]=32'hc76c51a3;
        K[44]=32'hd192e819; K[45]=32'hd6990624; K[46]=32'hf40e3585; K[47]=32'h106aa070;
        K[48]=32'h19a4c116; K[49]=32'h1e376c08; K[50]=32'h2748774c; K[51]=32'h34b0bcb5;
        K[52]=32'h391c0cb3; K[53]=32'h4ed8aa4a; K[54]=32'h5b9cca4f; K[55]=32'h682e6ff3;
        K[56]=32'h748f82ee; K[57]=32'h78a5636f; K[58]=32'h84c87814; K[59]=32'h8cc70208;
        K[60]=32'h90befffa; K[61]=32'ha4506ceb; K[62]=32'hbef9a3f7; K[63]=32'hc67178f2;
    end

    // 工作寄存器
    reg [31:0] a, b, c, d, e, f, g, h;
    reg [5:0]  round;
    reg        running;

    // 消息调度
    reg [31:0] W [0:63];
    integer i;

    // 辅助函数
    function [31:0] rotr;
        input [31:0] x;
        input [4:0]  n;
        rotr = (x >> n) | (x << (32 - n));
    endfunction

    function [31:0] shr;
        input [31:0] x;
        input [4:0]  n;
        shr = x >> n;
    endfunction

    wire [31:0] sigma0 = rotr(W[round-2], 7) ^ rotr(W[round-2], 18) ^ shr(W[round-2], 3);
    wire [31:0] sigma1 = rotr(W[round-15],17) ^ rotr(W[round-15],19) ^ shr(W[round-15],10);
    wire [31:0] big_s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
    wire [31:0] big_s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
    wire [31:0] ch     = (e & f) ^ (~e & g);
    wire [31:0] maj    = (a & b) ^ (a & c) ^ (b & c);

    wire [31:0] t1 = h + big_s1 + ch + K[round] + W[round];
    wire [31:0] t2 = big_s0 + maj;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            running <= 0; round <= 0; valid <= 0;
            hash_out <= 0;
        end else if (start && !running) begin
            // 初始化
            a <= hash_in[255:224]; b <= hash_in[223:192];
            c <= hash_in[191:160]; d <= hash_in[159:128];
            e <= hash_in[127:96];  f <= hash_in[95:64];
            g <= hash_in[63:32];   h <= hash_in[31:0];
            // 加载消息字
            for (i = 0; i < 16; i = i + 1)
                W[i] <= block_in[511 - i*32 -: 32];
            round <= 0; running <= 1; valid <= 0;
        end else if (running) begin
            if (round < 64) begin
                // 消息调度扩展
                if (round >= 16)
                    W[round] <= sigma1 + W[round-7] + sigma0 + W[round-16];
                // 压缩
                h <= g; g <= f; f <= e;
                e <= d + t1; d <= c; c <= b; b <= a;
                a <= t1 + t2;
                round <= round + 1;
            end else begin
                hash_out <= {a+hash_in[255:224], b+hash_in[223:192],
                             c+hash_in[191:160], d+hash_in[159:128],
                             e+hash_in[127:96],  f+hash_in[95:64],
                             g+hash_in[63:32],   h+hash_in[31:0]};
                valid <= 1; running <= 0;
            end
        end
    end

endmodule

4. 测试台

// sha256_compress_tb.v
module sha256_compress_tb;

    reg clk, rst_n, start;
    reg [255:0] hash_in;
    reg [511:0] block_in;
    wire [255:0] hash_out;
    wire valid;

    sha256_compress uut (
        .clk(clk), .rst_n(rst_n), .start(start),
        .hash_in(hash_in), .block_in(block_in),
        .hash_out(hash_out), .valid(valid)
    );

    always #5 clk = ~clk;

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

        // SHA-256 初始哈希值
        hash_in = {32'h6a09e667, 32'hbb67ae85, 32'h3c6ef372, 32'ha54ff53a,
                   32'h510e527f, 32'h9b05688c, 32'h1f83d9ab, 32'h5be0cd19};

        // 空消息填充块 (len=0, padding only)
        block_in = 512'h00000000000000000000000000000000
                       00000000000000000000000000000000
                       00000000000000000000000000000000
                       00000000000000008000000000000000;

        start = 1; #10; start = 0;

        wait(valid);
        $display("Hash: %064h", hash_out);
        // 期望: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
        $finish;
    end

endmodule

1. 用空消息验证压缩函数输出是否等于 SHA-256("")[的]前半部分。

2. 实现消息调度为移位寄存器版本:只用 16 个寄存器,每轮移出最旧的消息字。

3. 计算 SHA-256 压缩函数的关键路径:从 h 到新的 a 需要经过多少级加法?

4. 思考:如果攻击者可以控制消息块,能否通过选择特定 W[t] 来降低压缩函数的安全性?

🏆 成就解锁:哈希核心

你已实现 SHA-256 压缩函数的硬件核心,掌握了消息调度、64 轮迭代和辅助函数的硬件映射。这是哈希算法的心脏!

获得徽章:🧬 HASH_CORE

💡 扩展阅读与参考资源

🔧 实践环境搭建

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

# 安装 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. SHA-256 的安全分析

5.1 长度扩展攻击

SHA-256 基于 Merkle-Damgård 结构,存在长度扩展攻击:给定 H(m),攻击者可以计算 H(m ‖ padding ‖ m') 而不知道 m。

这就是为什么 H(K ‖ m) 不是安全的 MAC——必须使用 HMAC。

5.2 碰撞抵抗

SHA-256 的 256 位输出意味着生日攻击需要约 2¹²⁸ 次哈希计算才能找到碰撞。目前没有已知的实用碰撞攻击。

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