您的位置:首页 > 房产 > 家装 > 基于FPGA的SM3加密算法优化(SM3加密算法三)

基于FPGA的SM3加密算法优化(SM3加密算法三)

2024/10/6 18:26:49 来源:https://blog.csdn.net/weixin_50810761/article/details/139454311  浏览:    关键词:基于FPGA的SM3加密算法优化(SM3加密算法三)

  前文根据奇哥的方法使用FPGA实现了SM3加密算法,算法实现方式正确,但在并行度为2的情况下,在zynq7030ffg676-2也只能跑到50MHz,并行度为1也跑不到100MHz。

  因此在了解原理的过程中,发现消息扩展和迭代过程其实可以全部放入流水线中,并行度固定为1,最终设计能够跑到200MHz,下面就讲解代码实现过程。

1、代码讲解

  首先是端口信号,根据参数DATA_W设置输入数据位宽,取值范围是[1,447],支持对这个范围内所有数据加密。

module sm3_encrypt #(parameter			DATA_W      =       9'd256	             //待加密数据位宽,取值范围[1,447]。
)(input									clk		            ,//系统时钟信号;input									rst_n 	            ,//系统复位信号,低电平有效;input				[DATA_W - 1 : 0]    din		            ,//输入待加密数据;input									din_vld	            ,//输入待加密数据有效指示信号,高电平有效;output reg			[255 : 0]	        dout	            ,//输出加密结果数据信号;output reg								dout_vld             //输出加密结果数据有效指示信号,高电平有效;
);

  然后是整个加密过程中会使用到的函数,如下所示,与前文代码一致。

    //生成置换函数P1function [31 : 0] P1;input [31 : 0] X;begin//X异或X循环左移15位,在异或X循环左移23位;P1 = X ^ {X[16:0],X[31:17]} ^ {X[8:0],X[31:9]};endendfunction//计算置换函数P0function [31 : 0] P0;input [31 : 0] X;begin//计算公式:P0=X ^ (X <<< 9) ^ (X <<< 17)P0 = X ^ {X[22:0],X[31:23]} ^ {X[14:0],X[31:15]};endendfunction//计算布尔函数FFj(x,y,z);function [31 : 0] FFj;input [31 : 0] X;input [31 : 0] Y;input [31 : 0] Z;input [31 : 0] j;    beginFFj = (j < 16) ? (X ^ Y ^ Z) : ((X & Y) | (X & Z) | (Y & Z));endendfunction//计算布尔函数GGj(x,y,z);function [31:0] GGj;input [31 :0] X;input [31 :0] Y;input [31 :0] Z;input [31 :0] j;    beginGGj = (j < 16) ? (X ^ Y ^ Z) : ((X & Y) | (~X & Z));endendfunction//常量Tj;function [31:0] Tj;input [31:0] j;beginTj = (j < 16) ? 32'h79cc4519 : 32'h7a879d8a;endendfunction

  使用移位寄存器将输入数据有效指示信号暂存,与后续填充数据和大端转换数据对齐。

    //使用移位寄存器将输入数据暂存;always@(posedge clk)begindin_vld_r[2:0] <= {din_vld_r[1:0],din_vld};end

  把输入数据填充到512比特,对应代码如下所示。注意工程在运行时必须保证待加密数据位宽与设置的输入数据位宽一致,否则以设置的输入数据位宽为准。

    always@(posedge clk)beginif(~rst_n)beginpadding_data <= 'd0;endelse begin//进行填充,在数据后面加1'b1,然后将数据补足448位,最后64位存储数据长度;padding_data <= {din[DATA_W - 1 : 0],1'b1,{{447-DATA_W}{1'b0}},{55'd0,DATA_W[8:0]}};endend

  数据在扩展和迭代过程中都是以大端模式进行存储,因此需要先将输入小端数据转换为大端数据,对应代码如下所示。

    genvar g_i;generatefor(g_i = 0 ; g_i < 16 ; g_i = g_i + 1)begin : encodealways@(posedge clk)beginif(din_vld_r[0])//将输入的小端数据转换为大端数据;padding_data_big[g_i*32+31 : g_i*32] = padding_data[511-g_i*32 : 480-g_i*32];endendendgenerate

  前面这部分与前文基本一致,修改部分主要在消息扩展,迭代必须每个时钟迭代一轮,因此也没太多修改的地方。

  如下图所示,上面是消息扩展部分,下面是迭代过程,前文是通过一个时钟计算出wi(1667)和wi’(063)所有的结果。经过下面不难看出,每次迭代只使用了消息扩展wi和wi’中的一个数据,那么是不是每个时钟也只需要计算一个wi和wi’的数据即可呢?

  而且消息扩展的大部分延时都在计算wi这部分,那是不是可以在这段计算过程中多插入几级触发器呢?首先wi(016)是消息填充后的数据,表示在后面的64轮迭代过程中,其实只有53个时钟需要计算wi(1667)的值,完全可以插入几级触发器。

在这里插入图片描述

图1 消息扩展和迭代过程

  计算wi’只需要wi和wi+4即可,只需要在迭代开始时,将wi‘0提前计算出即可,后续数据可以在迭代的同时计算出。

  整体的修改思路就是这样,在消息扩展中插入触发器,将一个时钟的并行计算分为64个时钟串行计算,并且不会影响迭代效率。

  对应代码如下所示,首先把大端转换完成的数据存入wi[0~15]中,作为后续运算的数据。

    genvar g_exi;generate//将输入的低16个字存入扩展结果中;for(g_exi = 0 ; g_exi < 16 ; g_exi = g_exi + 1)begin : Extend_Word1always@(posedge clk)beginif(~rst_n)extend_data_m[g_exi] <= 'd0;else if(din_vld_r[1])extend_data_m[g_exi] <= padding_data_big[(g_exi * 32) + 31 : g_exi * 32];endendendgenerate

  然后计算出wi’[0]便于后续第一轮迭代使用。

    //计算第一个w'(0)always@(posedge clk)beginif(~rst_n)extend_data_m0[0] <= 'd0;else if(din_vld_r[2])//将输入的低16个字存入扩展结果中;extend_data_m0[0] <= extend_data_m[0] ^ extend_data_m[4];end

  将迭代运算的初始值存入ai[0]~hi[0],作为迭代运算的初始值。

    //迭代的初始值;always@(posedge clk)beginif(~rst_n)begin{ai[0],bi[0],ci[0],di[0],ei[0],fi[0],gi[0],hi[0]} <= 256'h7380166f4914b2b9172442d7da8a0600_a96f30bc163138aae38dee4db0fb0e4e;endend

  下面的cmp_vld用于指示当前进行的迭代轮数,对应位为高电平表示正在进行对应轮的迭代。

    //表示正在进行计算;always@(posedge clk)begincmp_vld[64:0] <= {cmp_vld[63:0],din_vld_r[2]};end

  下面的循环内部包含每个时钟需要完成的消息扩展和迭代运算。

    genvar i;generatefor(i = 0 ; i < 64 ; i = i + 1)begin : I//每个时钟完成一次扩展运算;if(i < 52)beginalways@(posedge clk)beginif(~rst_n)p1_x[i] <= 'd0;else if(cmp_vld[i])//用于计算p1(0)~p1(52)的参数;p1_x[i] <= extend_data_m[i] ^ extend_data_m[i+7] ^ {extend_data_m[i+13][16:0],extend_data_m[i+13][31:17]};endend//计算P1的结果;if((i > 0) && (i < 53))beginalways@(posedge clk)beginif(~rst_n)p1[i - 1] <= 'd0;else if(cmp_vld[i])//用于计算p1(0)~p1(52)的参数;p1[i - 1] <= P1(p1_x[i - 1]);endend//每个时钟完成一次扩展运算;if((i > 1) && (i < 54))beginalways@(posedge clk)beginif(~rst_n)extend_data_m[14 + i] <= 'd0;else if(cmp_vld[i])//用于计算w(16)~w(67)的数据;extend_data_m[14 + i] <= (p1[i - 2]) ^ {extend_data_m[i+1][24:0],extend_data_m[i+1][31:25]} ^ extend_data_m[i+8];endend//每个时钟完成一次扩展运算;if(i < 63)beginalways@(posedge clk)beginif(~rst_n)extend_data_m0[i + 1] <= 'd0;else if(cmp_vld[i])//用于计算w'(1)~w'(63)的数据;extend_data_m0[i + 1] <= extend_data_m[i + 1] ^ extend_data_m[i + 5];endend/**************************** 消息扩展结束 ****************************//**************************** 迭代运算部分开始 ****************************/assign w_Tj[i] = Tj(i);//计算Tiassign w_Tj_y[i] = (i[4:0]) ? ({w_Tj[i][31 - i[4:0] : 0],w_Tj[i][31:32 - i[4:0]]}) : w_Tj[i];//计算(Ti<<<(jmod32))assign w_ss1_mid0[i] = {ai[i][19:0],ai[i][31:20]} + ei[i] + w_Tj_y[i];//计算(A<<<12)+E+(Ti<<<(jmod32));assign w_ss1[i] = {w_ss1_mid0[i][24:0] , w_ss1_mid0[i][31:25]};//计算SS1=((A<<<12)+E+(Ti<<<(jmod32)))<<<7;assign w_ss2[i] = w_ss1[i] ^ {ai[i][19:0],ai[i][31:20]};//计算SS2=SS1^(A<<<12);assign w_tt1[i] = FFj(ai[i],bi[i],ci[i],i) + di[i] + w_ss2[i] + extend_data_m0[i];//计算TT1=FFi(A,B,C)+D+SS2+W';assign w_tt2[i] = GGj(ei[i],fi[i],gi[i],i) + hi[i] + w_ss1[i] + extend_data_m[i];//计算TT2=GGi(E,F,G)+H+SS1+Wi;always@(posedge clk)beginif(~rst_n){ai[i+1],bi[i+1],ci[i+1],di[i+1],ei[i+1],fi[i+1],gi[i+1],hi[i+1]} <= 'd0;else if(cmp_vld[i])//用于计算w'(1)~w'(63)的数据;{ai[i+1],bi[i+1],ci[i+1],di[i+1],ei[i+1],fi[i+1],gi[i+1],hi[i+1]} <= {w_tt1[i],ai[i],{bi[i][22:0],bi[i][31:23]},ci[i],P0(w_tt2[i]),ei[i],{fi[i][12:0],fi[i][31:13]},gi[i]};end/**************************** 迭代运算部分结束 ****************************/endendgenerate

  使用三级流水线来计算消息扩展结果wi[16~67],首先计算P1函数的系数,对应代码如下。

    //每个时钟完成一次扩展运算;if(i < 52)beginalways@(posedge clk)beginif(~rst_n)p1_x[i] <= 'd0;else if(cmp_vld[i])//用于计算p1(0)~p1(52)的参数;p1_x[i] <= extend_data_m[i] ^ extend_data_m[i+7] ^ {extend_data_m[i+13][16:0],extend_data_m[i+13][31:17]};endend

  下个时钟将上个时钟得到的p1系数带入函数,计算得到p1函数的结果。

    //计算P1的结果;if((i > 0) && (i < 53))beginalways@(posedge clk)beginif(~rst_n)p1[i - 1] <= 'd0;else if(cmp_vld[i])//用于计算p1(0)~p1(52)的参数;p1[i - 1] <= P1(p1_x[i - 1]);endend

  然后再完成表达式P1 (Wi-16异或Wi-9异或(Wi-3<<<15))异或(Wi-13<<<7)异或Wi-6的计算,对应代码如下。

    //每个时钟完成一次扩展运算;if((i > 1) && (i < 54))beginalways@(posedge clk)beginif(~rst_n)extend_data_m[14 + i] <= 'd0;else if(cmp_vld[i])//用于计算w(16)~w(67)的数据;extend_data_m[14 + i] <= (p1[i - 2]) ^ {extend_data_m[i+1][24:0],extend_data_m[i+1][31:25]} ^ extend_data_m[i+8];endend

  下面是通过wi计算wi’的代码,依旧是采用流水线设计,每个时钟计算一个数据。

    //每个时钟完成一次扩展运算;if(i < 63)beginalways@(posedge clk)beginif(~rst_n)extend_data_m0[i + 1] <= 'd0;else if(cmp_vld[i])//用于计算w'(1)~w'(63)的数据;extend_data_m0[i + 1] <= extend_data_m[i + 1] ^ extend_data_m[i + 5];endend

  上述完成了消息扩展,下面就是迭代的内容,由于迭代必须在一个时钟完成,而且里面的函数需用到上次迭代的结果,导致中间无法插入触发器(插入触发器会导致几个时钟才能完成一次迭代,会减小数据吞吐量),因此这部分依旧使用组合逻辑实现,与前文并没有什么改变。

  下面是计算ss1、ss2、tt1、tt2的代码,直接与图1的迭代过程对比即可。

    assign w_Tj[i] = Tj(i);//计算Tiassign w_Tj_y[i] = (i[4:0]) ? ({w_Tj[i][31 - i[4:0] : 0],w_Tj[i][31:32 - i[4:0]]}) : w_Tj[i];//计算(Ti<<<(jmod32))assign w_ss1_mid0[i] = {ai[i][19:0],ai[i][31:20]} + ei[i] + w_Tj_y[i];//计算(A<<<12)+E+(Ti<<<(jmod32));assign w_ss1[i] = {w_ss1_mid0[i][24:0] , w_ss1_mid0[i][31:25]};//计算SS1=((A<<<12)+E+(Ti<<<(jmod32)))<<<7;assign w_ss2[i] = w_ss1[i] ^ {ai[i][19:0],ai[i][31:20]};//计算SS2=SS1^(A<<<12);assign w_tt1[i] = FFj(ai[i],bi[i],ci[i],i) + di[i] + w_ss2[i] + extend_data_m0[i];//计算TT1=FFi(A,B,C)+D+SS2+W';assign w_tt2[i] = GGj(ei[i],fi[i],gi[i],i) + hi[i] + w_ss1[i] + extend_data_m[i];//计算TT2=GGi(E,F,G)+H+SS1+Wi;

  后面是将迭代的计算结果存入ai[i+1]~hi[i+1]中,作为下轮迭代的初始值。

    always@(posedge clk)beginif(~rst_n){ai[i+1],bi[i+1],ci[i+1],di[i+1],ei[i+1],fi[i+1],gi[i+1],hi[i+1]} <= 'd0;else if(cmp_vld[i])//用于计算w'(1)~w'(63)的数据;{ai[i+1],bi[i+1],ci[i+1],di[i+1],ei[i+1],fi[i+1],gi[i+1],hi[i+1]} <= {w_tt1[i],ai[i],{bi[i][22:0],bi[i][31:23]},ci[i],P0(w_tt2[i]),ei[i],{fi[i][12:0],fi[i][31:13]},gi[i]};end

  将64轮迭代的结果ai[64]hi[64]与迭代初始值ai[0]hi[0]异或得到加密结果,对应代码如下,同时生成加密结果有效指示信号。

    always@(posedge clk)beginif(~rst_n)begindout  <= 'd0;endelse begin//将迭代计算的最终结果与初始值异或,得到加密结果;dout  <= {ai[64],bi[64],ci[64],di[64],ei[64],fi[64],gi[64],hi[64]} ^ {ai[0],bi[0],ci[0],di[0],ei[0],fi[0],gi[0],hi[0]};endendalways@(posedge clk)begindout_vld <= cmp_vld[64];end

2、代码仿真

  顶层模块和仿真激励文件生成模块依旧使用前文的代码,只需要修改模块的参数即可。

  顶层模块代码如下所示:

//--###############################################################################################
//--#
//--# File Name		: top
//--# Designer		: 数字站
//--# Tool			: Vivado 2021.1
//--# Design Date	: 2024.6.2
//--# Description	: sm3加密顶层模块
//--# Version		: 0.0
//--# Coding scheme	: GBK(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode)
//--#
//--###############################################################################################
module top (input			        clk		        ,//系统时钟信号,默认100MHz;input			        rst_n	        ,//系统复位信号,低电平有效;output   reg            led             
);(* MARK_DEBUG = "TRUE" *)reg     [255 : 0]       Original_Data   ;(* MARK_DEBUG = "TRUE" *)reg                     Original_Valid  ;reg     [3  : 0]       cnt             ;(* MARK_DEBUG = "TRUE" *)wire    [255 : 0]       Encrypt_Data    ;(* MARK_DEBUG = "TRUE" *)wire                    Encrypt_Valid   ;sm3_encrypt #(.DATA_W         ( 256            ) //待加密数据位宽,取值范围[1,447]。)u_sm3_encrypt (.clk	        ( clk           ),//系统时钟信号;.rst_n 	        ( rst_n         ),//系统复位信号,低电平有效;.din	        ( Original_Data ),//24'h616263    ),//输入待加密数据;.din_vld        ( Original_Valid),//输入待加密数据有效指示信号,高电平有效;.dout	        ( Encrypt_Data  ),//输出加密结果数据信号;.dout_vld       ( Encrypt_Valid ) //输出加密结果数据有效指示信号,高电平有效;);//循环产生测试数据的时钟信号;always@(posedge clk)beginif(~rst_n)cnt <= 'd0;else cnt <= cnt + 'd1;end//每间隔128个时钟生成一个测试数据;always@(posedge clk)beginif(~rst_n)beginOriginal_Data  <= 256'h0001020304050607_08090a0b0c0d0e0f_0001020304050607_08090a0b0c0d0e0f;Original_Valid <= 'd0;end else if(&cnt)beginOriginal_Data  <= Original_Data + 3;Original_Valid <= 'd1;end else beginOriginal_Valid <= 'd0;endend//防止Encrypt_Data被优化掉;always@(posedge clk)beginif(~rst_n)begin//初始值为0;led <= 'd0;endelse if(Encrypt_Valid)beginled <= (Encrypt_Data > 10000);endendendmodule

  运行仿真结果如下所示,加密数据为256’h0001020304050607_08090a0b0c0d0e0f_0001020304050607_08090a0b0c0d0e12,最后加密结果为256’h3C6F866ABC77AF25_F3EE32C4864BDAE506_F4A946197D774D45_ACD127797360A6。

在这里插入图片描述

图2 仿真结果

  使用网页加密的结果如下所示,与上图仿真保持一致,证明上述设计的逻辑运算应该没有问题。

在这里插入图片描述

图3 网页加密

3、上板测试

  首先综合工程,然后分配管脚,添加ILA,加入时钟约束,当前系统时钟约束为200MHz,如下图所示,然后对工程进行布局布线,最后生成比特流文件。

在这里插入图片描述

图4 时钟约束

  查看时序报告,如下所示,建立时间余量和保持时间余量均大于零,表示该设计工作在200MHz时钟下没有问题。

在这里插入图片描述

图5 时序报告

  将比特流文件下载到开发板,使用ILA抓取一帧数据,如下所示。待加密数据为256’h0001020304050607_08090a0b0c0d0e0f_0001020 304050607_08090a0b0d0856a3,抓取加密结果为256’h 2FE2A06A9075CC30_30BA74879761D179_60AD30C6ABA57F37_ECA4410AA A1E0864。

在这里插入图片描述

图6 ILA抓取加密时序

  使用加密工具验证结果如下所示,与上图加密结果一致,表示加密正常。

在这里插入图片描述

图7 网页加密结果

  使用ILA再抓取一帧数据,如下所示。待加密数据为256’h0001020304050607_08090a0b0c0d0e0f_0001020304050607_08090a0 b341f75ca,抓取加密结果为256’h D9E6BCD11C87679B_D07B230BC9368229_6537E97CFD774AAD_A54ABDAEBA935C1B。

在这里插入图片描述

图8 ILA抓取加密时序

  使用加密工具验证结果如下所示,与上图加密结果一致,表示加密正常。

在这里插入图片描述

图9 网页加密结果

  在讲解SM3原理时,对24’h616263进行了加密,本文对该加密结果进行验证,首先把顶层模块的输入数据位宽修改为24位,输入数据改为固定的24’h616263,如下图所示。

在这里插入图片描述

图10 修改加密输入数据

  然后重新综合工程,将生成的比特流下载到FPGA中,使用ILA抓取加密结果如下所示,加密结果为256’h 66C7F0F462EEEDD9_D1F2 D46BDC10E4E2_4167C4875CF2F7A2_297DA02B8F4BA8E0。

在这里插入图片描述

图11 24’h616263的加密结果

  最后使用网页工具验证24’h616263的加密结果,如下所示,与上图ILA抓取的结果一致,表示加密正确,与前文原理讲解时的加密结果也是一致的。

在这里插入图片描述

图12 网页加密结果

  虽然该工程在zynq7030ffg676-2上可以正常工作在200MHz时钟下,但是输入有效数据间隔时间必须大于等于16个时钟,因为消息扩展的低16个数据必须被用于迭代计算后才能输入新的数据,否则这些数据会被覆盖掉,导致计算错误。

  通过把消息扩展的低16个数据使用触发器保存一段时间,不会被后输入的数据覆盖,来达到支持连续数据输入的目的。但是会消耗更多的资源,奇哥的代码由于消息扩展在一个时钟就被计算出来,然后保存了,因此下一个数据输入不会影响上次输入的数据计算。

  本文的工程可以在公众号后台回复“SM3算法的流水线优化”(不包括引号)获取。


  如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!

  如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com