遇到的问题:verilog 语言在设计循环时不能采用类似 C 语言中的 for 循环的设计思想
解决方法:通过设置计数器和标志位来解决,当计数器到达设定的值时,改变标志位,检测到标志位改变时,退出循环。
实验报告亮点:在波形图中直观地展示出 Grain128 算法初始化和输出的过程,同时设计了很多标志位,来记录每个阶段的结束过程。
实验环境
- Ubuntu 22.04.4 LTS (wsl2)
- iverilog 11.0 (stable)
- GTKWave Analyzer v3.3.104
- vscode
算法原理
Grain算法是一种基于寄存器的流密码算法,其设计思路主要包括三个部分:序列产生、密钥更新和密钥生成。在序列产生过程中,通过线性反馈移位寄存器(LFSR)和非线性反馈移位寄存器(NFSR)产生伪随机序列。密钥更新过程使用非线性函数对寄存器中的数据进行变换和混合。密钥生成过程使用产生的伪随机序列与明文进行异或运算,得到密文。
算法流程

其中,$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轮初始化后,开始输出密钥流,如下图所示:

波形图
使用 gtkwave 查看生成的波形文件,如下图所示:
clk为时钟信号,reset为复位信号,当reset为低电平时,移位寄存器开始更新,同时初始化计数器开始计数,当初始化计数器为255时,初始化完成,init_done置为1,如下图:
此时开始输出密钥流,可以发现与上面的运行结果相吻合。
总结
我们使用硬件编程语言verilog实现了一个流密码算法——Grain128,并编写了testbench进行了测试,借助工具iverilog
和gtkwave
,实现了仿真并查看运行时的波形图,Grain128是一个轻量化的硬件高效的流密码算法,具有很低的门数消耗,同时通过LFSR提供的线性随机性和NFSR提供的非线性随机性组合生成密钥流,确保了安全性,同时保持了极小的硬件占用。通过本次实验,我进一步理解了轻量级密码算法在硬件上的实现方式,并对流密码算法有了更加深刻的理解。