遇到的问题:verilog 语言在设计循环时不能采用类似 C 语言中的 for 循环的设计思想

解决方法:通过设置计数器和标志位来解决,当计数器到达设定的值时,改变标志位,检测到标志位改变时,退出循环。

实验报告亮点:在波形图中直观地展示出 Grain128 算法初始化和输出的过程,同时设计了很多标志位,来记录每个阶段的结束过程。


实验环境

  • Ubuntu 22.04.4 LTS (wsl2)
  • iverilog 11.0 (stable)
  • GTKWave Analyzer v3.3.104
  • vscode

算法原理

Grain算法是一种基于寄存器的流密码算法,其设计思路主要包括三个部分:序列产生、密钥更新和密钥生成。在序列产生过程中,通过线性反馈移位寄存器(LFSR)和非线性反馈移位寄存器(NFSR)产生伪随机序列。密钥更新过程使用非线性函数对寄存器中的数据进行变换和混合。密钥生成过程使用产生的伪随机序列与明文进行异或运算,得到密文。

算法流程

image-20240929172707732

其中,$f(x)$,$g(x)$,$h(x)$,如下所示:

  • 线性反馈移位寄存器(LFSR)的反馈多项式$f(x)$

$$ f(x) = 1 + x^{32} + x^{47} + x^{58} + x^{90} + x^{121} + x^{128} $$

  • 非线性反馈移位寄存器 (NFSR) 的反馈多项式 $g(x)$

$$ g(x) = 1 + x^{32} + x^{37} + x^{72} + x^{102} + x^{128} + x^{44}x^{60} + x^{61}x^{125} + x^{63}x^{67} + x^{69}x^{101} + x^{80}x^{88} + x^{110}x^{111} + x^{115}x^{117} $$

  • 布尔函数 $h(x)$

$$ h(x) = x_0x_1 + x_2x_3 + x_4x_5 + x_6x_7 + x_0x_4x_8 $$

  • 输出$z_i$

$$ z_i = \sum_{j \in A} b_{i+j} + h(x) + s_{i+93}\ 其中~A = {2, 15, 36, 45, 64, 73, 89} $$

1. 密钥key和IV初始化

  • LFSR的前96位装载IV的96位,剩余的32位填充为1。
  • NFSR装载128位的密钥。
  • 密钥和IV加载后,密码运行256次时钟周期,但不生成密钥流,而是反馈输出并与LFSR和NFSR的输入异或。

2. 移位寄存器的更新

  • LFSR使用一个128阶的原始多项式进行更新,确保密钥流的统计性质良好,并提供较长的周期。
  • NFSR通过一个非线性反馈函数更新,提供了额外的非线性来增强密码的安全性。

3. 输出函数

  • 输出函数从LFSR和NFSR的多个位中获取输入,并通过布尔函数 $h(x)$ 生成一个输出位。这个输出位与从寄存器中选择的其他位相加,最终生成密钥流。

4. 密钥流生成

  • 在完成初始化后,系统开始正常工作,LFSR和NFSR按固定步数更新,每个时钟周期输出1位密钥流。

算法实现

项目结构

➜  Grain128 tree
.
├── grain128.v
├── grain128.vcd
├── grain128.vvp
└── grain128_tb.v
  • grain128.v:Grain128 算法的 Verilog 源代码
  • grain128_tb.v:测试平台,用于验证算法的设计
  • grain128.vvp:由 Verilog 编译器生成的仿真可执行文件
  • grain128.vcd:仿真产生的波形文件,用于分析信号变化

Grain128 算法的verilog实现

Grain128模块声明

module grain128(
    input wire clk,
    input wire reset,
    input wire [127:0] key,
    input wire [95:0] iv,
    output reg keystream
);
  • clk:时钟信号,携带时钟脉冲信号来驱动整个电路的同步操作。
  • reset:复位信号,用于算法的启动。
  • key:大小为128bit,用于输入算法的密钥。
  • iv:大小为96bit,用于输入初始向量。
  • keystream:输出信号,用于输入生成的密钥流。

其他变量说明

reg [127:0] LFSR; //LFSR寄存器
reg [127:0] NFSR; //NFSR寄存器
wire FEEDBACK_LFSR; //存储经过LFSR反馈多项式后的结果
wire FEEDBACK_NFSR; //存储经过NFSR反馈多项式后的结果
reg [7:0] init_counter; //用于算法初始化的计数器
reg [127:0] counter; //记录反馈总次数的计数器
reg init_done; //记录初始化是否完成的标志位

反馈多项式以及输出$z_i$实现

  • $h(x)$
function h;
    input [127:0] LFSR,NFSR;
    begin
        //h(x)=b12*S8+S13*S12+b95*S42+b60*S79+b12*b95*S94
         h = NFSR[12] ^ LFSR[8] ^ (LFSR[13] & LFSR[12]) ^ (NFSR[95] & LFSR[42]) ^ (LFSR[60] & LFSR[79]) ^ (NFSR[12] & NFSR[95] & LFSR[94]);
    end   
endfunction
  • $f(x)$
//f(x)=1+x^32+x^47+x^58+x^90+x^121+x^128
assign FEEDBACK_LFSR = LFSR[0] ^ LFSR[32] ^ LFSR[47] ^ LFSR[58] ^ LFSR[90] ^ LFSR[121] ^LFSR[127];
  • $g(x)$
//g(x)=1+x^32+x^37+x^72+x^102+x^128+x^44x^60+x^61x^125+x^63x^67x^69x^101+x^80x^88+x^110x^111+x^115x^117+x^40x^50x^58x^101+x^103x^104x^106+x^33x^35x^36x^40
assign FEEDBACK_NFSR = NFSR[0] ^ NFSR[32] ^ NFSR[37] ^ NFSR[72] ^ NFSR[102] ^ NFSR[127] ^
                       (NFSR[44] & NFSR[60]) ^ (NFSR[61] & NFSR[125]) ^
                       (NFSR[63] & NFSR[67] & NFSR[69] & NFSR[101]) ^
                       (NFSR[80] & NFSR[88]) ^ (NFSR[110] & NFSR[111]) ^
                       (NFSR[115] & NFSR[117]) ^ 
                       (NFSR[40] & NFSR[50] & NFSR[58] & NFSR[101]) ^
                       (NFSR[103] & NFSR[104] & NFSR[106]) ^
                       (NFSR[33] & NFSR[35] & NFSR[36] & NFSR[40]);
  • 输出$z_i$

$$ z_i = \sum_{j \in A} b_{i+j} + h(x) + s_{i+93}\ 其中~A = {2, 15, 36, 45, 64, 73, 89} $$

wire Output = NFSR[(2+counter)%128] ^ NFSR[(15+counter)%128] ^ NFSR[(36+counter)%128] ^ NFSR[(45+counter)%128] ^ NFSR[(64+counter)%128] ^ NFSR[(73+counter)%128] ^ NFSR[(89+counter)%128] ^ h(LFSR, NFSR) ^ LFSR[(93+counter)%128]

算法流程实现

always @(posedge clk or posedge reset) begin
    // 参数初始化
    if (reset) begin
        LFSR <= {iv, 32'hFFFF_FFFF};  
        NFSR <= key;                  
        init_counter <= 8'd0;         
        init_done <= 1'b0;            
        counter <= 128'h0;             
        keystream <= 1'b0;             
    end
    // 256次初始化
    else if(!init_done) begin
        LFSR <= {LFSR[126:0], FEEDBACK_LFSR};  // LFSR移位并引入反馈
        NFSR <= {NFSR[126:0], FEEDBACK_NFSR};  // NFSR移位并引入反馈
        init_counter <= init_counter + 1;
        counter <= counter + 1;
        if (init_counter == 8'd255) begin
            init_done <= 1'b1;  // 当初始化计数器为255时,将init_done设置为1
        end
    end
    else begin
        LFSR <= {LFSR[126:0], FEEDBACK_LFSR};  
        NFSR <= {NFSR[126:0], FEEDBACK_NFSR};  
        counter <= counter + 1;
        keystream <= Output;  // 生成密钥流
    end
end

编写testbench

module testbench;
    reg clk;
    reg reset;
    reg [127:0] key;
    reg [95:0] iv;
    wire keystream;
    reg [8:0] init_counter;  // 扩展为9位计数器以计数256次初始化时钟周期
    reg init_done;           // 标志位,标识初始化是否完成

    grain128 uut(
        .clk(clk),
        .reset(reset),
        .key(key),
        .iv(iv),
        .keystream(keystream)
    );

    always #5 clk = ~clk;

    initial begin
        // 指定生成的VCD文件
        $dumpfile("grain128.vcd");
        // 开始记录波形数据
        $dumpvars(0, testbench);
        clk = 0;
        reset = 1;
        init_counter = 0;
        init_done = 0;  
        key = 128'h0123456789ABCDEF0123456789ABCDEF;
        iv = 96'hABCDEF0123456789ABCDEF;

        $display("Starting Grain128 Testbench");

        // 100时间单位时将复位信号置为0
        #100 reset = 0;
    end

    always @(posedge clk) begin
        // 当reset解除后,开始计数
        if (!reset && !init_done) begin
            if (init_counter < 256) begin
                init_counter <= init_counter + 1;  // 初始化阶段递增计数
            end else if (init_counter == 256) begin
                $display("Initialization complete at time %0t", $time);
                init_done <= 1;  // 设置标志位,表示初始化完成
            end
        end
    end

    always @(posedge clk) begin
        // 当初始化完成后,开始显示密钥流
        if (init_done) begin
            $display("Keystream at time %0t: %b", $time, keystream);
        end
    end

    initial begin
        // 运行一定时间后结束仿真
        #5000;
        $display("Grain128 Testbench finished");
        $finish;
    end

endmodule

编译运行

  • 使用命令iverilog -o grain128.vvp grain128_tb.v grain128.v编译并生成可执行文件
  • 使用命令vvp -n grain128.vvp来运行可执行文件
  • 使用命令gtkwave grain128.vcd查看生成的波形文件

运行结果

在256轮初始化后,开始输出密钥流,如下图所示:

image-20241007173126092

波形图

使用 gtkwave 查看生成的波形文件,如下图所示:

image-20241008170020122

clk为时钟信号,reset为复位信号,当reset为低电平时,移位寄存器开始更新,同时初始化计数器开始计数,当初始化计数器为255时,初始化完成,init_done置为1,如下图:

image-20241008170438701

此时开始输出密钥流,可以发现与上面的运行结果相吻合。

总结

我们使用硬件编程语言verilog实现了一个流密码算法——Grain128,并编写了testbench进行了测试,借助工具iveriloggtkwave,实现了仿真并查看运行时的波形图,Grain128是一个轻量化的硬件高效的流密码算法,具有很低的门数消耗,同时通过LFSR提供的线性随机性和NFSR提供的非线性随机性组合生成密钥流,确保了安全性,同时保持了极小的硬件占用。通过本次实验,我进一步理解了轻量级密码算法在硬件上的实现方式,并对流密码算法有了更加深刻的理解。