国钥SM3的Java程序实现_第1页
国钥SM3的Java程序实现_第2页
国钥SM3的Java程序实现_第3页
国钥SM3的Java程序实现_第4页
国钥SM3的Java程序实现_第5页
已阅读5页,还剩47页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

摘要SM3算法是我国自主研发的hash算法。本文将对SM3算法的加密原理及各个过程进行解析,并使用java语言设计程序实现SM3算法,将SM3算法的各个流程通过java函数进行实现,包括数据填充,分组和迭代压缩等。在这个过程中,通过SM3加密过程中存储数据所使用的java数据类型不同,设计出两种不同的SM3算法java实现:SM3-String方式,SM3-BigInteger方式。并对两种方式的优缺点进行分析。最后将设计出来的SM3加密程序与java语言自带的其他杂凑算法(MD5,SHA-256)实现进行对比,比较它们的运行效率。【关键词】hash算法;国钥SM3;java程序设计;AbstractSM3algorithmisahashalgorithmindependentlydevelopedinChina.ThispaperwillanalyzetheencryptionprincipleandeachprocessofSm3algorithm,andusejavalanguagetodesignprogramstorealizeSM3algorithm,andimplementeachprocessofSm3algorithmthroughJavafunctions,includingdatafilling,groupinganditerativecompression.Inthisprocess,throughthedifferentJavadatatypesusedtostoredataintheSM3encryptionprocess,twodifferentSM3algorithmjavaimplementationsaredesigned:SM3stringmodeandSM3BigIntegermode.Theadvantagesanddisadvantagesofthetwomethodsareanalyzed.Finally,theSM3encryptionprogramdesignediscomparedwithotherhashalgorithms(MD5,SHA-256)ofJavalanguage,andtheirrunningefficiencyiscompared.[Keywords]hashalgorithm;nationalkeySM3;Javaprogramming;目录目录第一章绪论 第一章绪论研究背景与意义哈希(Hash)算法,也叫散列函数或杂凑函数。它最大的特点是能将所有长度的信息转换成固定长度的哈希值,而且只能加密不能解密(只能单向运算)。是密码学里十分重要的分支,在数字签名、密码协议、完整性认证、消息鉴别、等领域起到了十分巨大的作用。哈希算法的种类与发展:MD2:1989年,RonaldL.Rivest开发出了MD2算法。MD2算法把需要加密的消息,先进行填充,使消息的字节长度是16的倍数,然后在末尾添加一个16位的校验和,然后进行运算,得出128位散列值(哈希值)。MD4:1990年,Rivest继MD2后又开发了更安全的MD4算法,MD4算法对消息的填充方式与MD2不同,它使填充后消息长度mod512=448,再将一个表示消息原来长度的64位二进制数填充到消息末尾。然后将消息按512比特一组进行分组,最后对每个分组进行三个不同步骤的处理,得出散列值(哈希值)长度与MD2相同。MD5:1991年,Rivest开发出技术上更为趋近成熟的MD5算法。MD5由MD4、MD2改进而来。虽然MD5的复杂度要比MD4大一些,但却更为安全。在MD5算法中,对消息进行填充分组的处理,和产生的散列值(哈希值)的长度与MD4相同。SHA-0和SHA-1:SHA系列的算法,由美国国家安全局(NSA)所设计,美国国家标准与技术研究院(NIST)发布,是美国的政府标准。1993年NSA发布了SHA-0,因为其存在安全问题,发布之后很快就被NSA撤回,在1995年NSA发布SHA-0的改进版SHA-1,SHA-1和SHA-0的算法只在压缩函数的讯息转换部分差了一个位元的循环位移。SHA-0和SHA-1可将一个最大为264的消息,处理成160比特的散列值(哈希值)。无论是MD2,MD4,MD5,还是SHA-1,他们生成的散列值(哈希值)都比较短,如MD5能生成的散列值(哈希值)只有128比特,这就意味着他们生成的散列值(哈希值)只有2128种,一旦加密的消息超过2128种,就必定会出现重复的散列值(哈希值)。这些算法安全性越来越满足不了时代的发展,也开始逐渐被更先进的hash算法取代。SHA-2:NSA于2001年发布SHA-2,其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。其中较为常见的是SHA-256和SHA-512,分别能生成256比特于512比特的散列值(哈希值)。国钥SM3:国钥算法也称国密算法,是指国家密码局认定的国产商用密码算法。而国钥SM3是国钥算法中唯一的散列算法(哈希算法),是王小云等人进行设计的,由国家密码局于2010年12月17日发布。SM3算法也会对消息进行填充分组处理,该过程与MD5,SHA-256一致,最终生成256位的散列值(哈希值)。因此,国钥SM3在安全性方面高于MD5,SHA-1,与SHA-256相当。SHA-2与国钥SM3都是目前安全性较高哈希算法,越来越的人根据这些算法研究开发出各种软件和硬件,并应用于各个行业和领域中。本文也是针对其中之一的国钥SM3算法进行编程实现。本文的总体内容本文主要使用java编程语言设计出能够进行SM3算法加密的程序,以下是本文的结构第一章 :绪论,主要概述本论文相关技术的研究背景与意义。介绍文章结构第二章 :对SM3算法的工作过程进行介绍与解析。第三章 :SM3算法的java程序实现。第四章 :对本文进行总结。第二章SM3算法过程解析2.1SM3算法简述SM3杂凑算法可将长度小于264比特的消息经过填充、反复的消息扩展和压缩,生成长度为256比特的杂凑值。2.2SM3算法具体过程消息填充:其原理是在填充一个1,若干个0,和一个64位的消息二进制长度,其中有65位是固定填充的,而填充0的个数用于保证填充后的消息长度是512的倍数。假设输入的消息m的长度为L比特。假设消息m的二进制为10101010,其长度为8,通过公式计算8+1+k≡448mod512,需要填充的0的个数k=439.所以最终进行的填充是:(1) 在消息m的末尾填充一个1(2) 在消息m的末尾填充439个0(3) 将8的二进制值1000,拓展到64位,填充到消息m的末尾图2-1为填充过程图解:图2-1填充过程迭代压缩:将m’按每组512比特进行分组:m’=B(0)B(1)…B(n-1),其中n=(l+k+65)/512.然后对m’按下列方式迭代:FORi=0TOn-1V(i+1)=CF(V(i),B(i))ENDFOR其中CF是压缩函数,v(0)为256bit初始值IV,值为0x7380166f,0x4914b2b9,0x172442d7,0xda8a0600,0xa96f30bc,0x163138aa,0xe38dee4d,0xb0fb0e4eB(i)为填充后的消息分组,迭代压缩的结果为V(n)消息拓展:在上一步迭代压缩中,每一步的for循环我们都会将一个512比特的分组B(i)传入压缩函数CF中,在CF中B(i)会进行以下的操作来拓展生成132个字W0,W1,…,W67,W’0,W’1,…,W’63,用于压缩函数CF的下一步操作:1. 生成W0-W16:将消息分组B(i)的二进制每32位划分为一个字,最终划分出16个字,作为W0-W16。2. 生成W16-W67:FORj=16TO67Wj←P1(Wj-16⊕Wj-9⊕Wj-3<<<15))⊕(Wj-13<<<7)⊕Wj-6ENDFOR3. 生成W’0-W’63:FORj=0TO63W’j=Wj⊕Wj+4ENDFOR压缩函数CF:A,B,C,D,E,F,G,H为能够存储32为比特的字寄存器,SS1,SS2,TT1,TT2为中间变量,其中存储的也是32为比特,压缩函数Vi+1=CF(V(i),B(i)),0≤i≤n-1。计算过程如下:ABCDEFGH←V(i)FORj=0TO63SS1←((A<<<12)+E+(Tj<<<j))<<<7SS2←SS1⊕(A<<<12)TT1←FFj(A,B,C)+D+SS2+Wj’TT2←GGj(E,F,G)+H+SS1+WjD←CC←B<<<9B←AA←TT1H←GG←F<<<19F←EE←P0(TT2)ENDFORV(i+1)←ABCDEFGH⊕Vi杂凑值(哈希值):ABCDEFGH←V(n)输出256比特的杂凑值y=ABCDEFGH。第三章SM3的java实现3.1程序核心方法SM3() //sm3核心方法,用String存储 privatestaticStringsm3(Stringm){ //调用填充函数 Stringm2=padding(m); //调用分组函数 int[][]m3=fenzu(m2); //调用压缩函数 int[]m4=diedaiyasuo(m3); Stringsm3hash=""; for(inti:m4){ sm3hash+=Integer.toHexString(i); } returnsm3hash; }sm3()方法就是整个sm3程序的主干,sm3算法的填充,分组,迭代压缩三个部分每部分都用一个独立的方法实现,sm3()核心方法的工作就是调用这些方法,之后将结果转换成16进制字符串的形式返回。sm3()方法使整个sm3算法的架构较为清晰,一定程度上降低了程序的耦合度。在测试阶段更容易定位问题的位置,日后若要对代码进行改进也更方便。3.2填充函数padding()填充函数的作用就是在消息后面填充一个1,若干个0,和一个64位的消息二进制长度,最终得出长度为512倍数的填充消息。整个填充过程,消息m都是用java的String字符串进行存储,一个String字符串由多个char字符组成,在填充时是以8比特的字符为单位对消息m进行填充 //sm3填充函数 privatestaticStringpadding(Stringm){ //mLen为信息m二进制的长度,sm3能处理消息长度最长为2的64次方bit,因此需要用java里长度为64位的long类型存储 longmLen=m.length()*8; //f是需要填充的0的个数 intf=(int)(512-(mLen+1+64)%512); //以字符为单位填充1和0 //0x80等于一个1和7个0 m+=(char)0x80; for(inti=0;i<(f-7)/8;i++){ m+=(char)0x00; } m+=longToString(mLen); returnm; }longToString()方法:由于sm3能处理的消息长度最大为264,所以用long来存储消息m的二进制长度mLen,方便计算需要填充0的个数,在运算完之后,需要填充mLen时,就需要把mLen的存储类型从long转为String,所以需要设计一个longToString()方法来完成类型转换。longToString()方法的思路就是对64位的long类型,通过java的位运算符,从高位到低位将每8位转成一个char字符,并拼接成字符串。longToString()方法的结果就是传入的long类型变量将转换成一个8字符的String字符串。如值为0110000101100010011000110110010001100101011001100110011101101000的long类型,通过该方法转换为String类型后为“abcdefgh”。//long类型转String类型 privatestaticStringlongToString(longnum){ Strings=""; for(inti=7;i>=0;i--){ s+=(char)((num>>i*8)&0xff); } returns; }测试填充结果:假设消息m为“usb”,其二进制为:011101010111001101100010,长度mlen=24,需要填充0的个数f=512-011101010111001101100010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011000前文提到过,该程序的填充,分组,迭代压缩都用独立的函数实现,所以可以编写一段代码单独调用填充函数来测试结果是否正确//测试填充函数publicstaticvoidtextpadding(){ Stringm="usb";//调用填充函数 Stringm2=padding(m); //打印填充结果的二进制 System.out.println("填充结果为:"); for(inti=0;i<m2.length();i++){ Stringm3=Integer.toBinaryString((int)(m2.charAt(i))); //由于java会自动略高位的0,所以手动补齐 while(8-m3.length()>0){ m3='0'+m3; } System.out.print(""+m3); if((i+1)%8==0){ System.out.println(); } }}输出结果与猜想一致,如图3-1.1:图3-1测试函数运行结果以字符为单位填充1和0,填充二进制位0x80代表一个1和7个0,之后的0每8个用二进制0x00进行填充,因为填充前的消息m是字符串,字符串由字符组成,字符的存储最小单位是字节(8比特),所以消息m二进制长度必定为8的倍数,填充后的消息二进制长度是512的倍数,是8的倍数,最后填充的消息长度固定是64位,也是8的倍数。因此填充中间的1和若干个0的一定也是的二进制长度也必定为8的倍数,由此证明用字符为单位进行1和0的填充并不会出现不满或溢出的情况,如图3-2所示:图3-2证明图解3.3分组函数fenzu()分组函数的作用是将填充后的消息按512比特为一组进行分组。在上一节填充里,填充后的消息是用String字符串进行存储的,根据一个字符8比特,分组后每一组都是一个长度为64的字符串。JavaString类的substring()方法可以获取到字符串的子串,分组函数就是同过该方法每获取消息的64长度的子串,并进行存储,以此实现分组的功能。为了方便后面迭代压缩的运算,设计出StringToIntArray()方法来将每组64长度的字符串转换成为一个长度为16的int数组。//分组函数 privatestaticint[][]fenzu(Stringm2){ //num:分组数,因java数组限制,分组数用32位的int存储 intnum=m2.length()/64; //将String转换成int[num][16],每个int[16]存储512bit, int[][]m3=newint[num][16]; for(inti=0;i<num;i++){ m3[i]=StringToIntArray(m2.substring(i*64,i*64+64)); } returnm3;}stringToIntArray()方法:为了方便后面迭代压缩的运算,设计出StringToIntArray()方法来将每组64长度的字符串转换成为一个长度为16的int数组。//将512bit的字符串转换为int[16]数组 privatestaticint[]StringToIntArray(Strings){ byte[]b; int[]a=newint[16]; try{ b=s.getBytes("ISO-8859-1"); for(inti=0;i<16;i++){ //byte[4]转int //&0xff是为了char变int时只取低八位,因为开头为1的byte数变int时高位会自动补1 a[15-i]=(int)((b[i*4]&0xff)<<24)|((b[i*4+1]&0xff)<<16)|((b[i*4+2]&0xff)<<8)|(b[i*4+3]&0xff); } }catch(UnsupportedEncodingExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } returna; }StringToIntArray()方法的思路是先用String类的getBytes()方法将String字符串转换为一个byte类型的数组,该数组每一个值都是一个8位的byte类型,而这里由于需要操作的字符串已经固定为512比特,也就是能转换为16个int值。因此循环16次,每次循环将char数组里的4个字符转换为一个int值。4个byte类型转换为1个int类型的思路如下:例如某一次循环中,b[j*4],b[j*4+1],b[j*4+2],b[j*4+3]在内存中的二进制值分别为01111111,00111111,00011111,00001111。将其分别左移24,16,8,0位得到:01111111000000000000000000000000001111110000000000000000000111110000000000001111将4个数进行或(|)运算,得到一个32位int类型的值:01111111001111110001111100001111“&0xff”的作用:在进行或运算时,进行了一次“&0xff”操作,0xff的值是11111111,理论上来所一个同样是8位的char类型的值对11111111进行&运算应该都为自己本身,那为什么还要进行这个操作?其实“&0xff”操作修改的并不是低8位,而是高位。在上述4个数或运算的过程中,可以发现这4个数的位数是不一样的,分别是32位,24位,16位和8位,为了成功进行或运算,计算机会自动将低位扩展成高位,因此实际进行或运算的四个数是:01100001000000000000000000000000000000000110001000000000000000000000000000000000011000110000000000000000000000000000000001100100而问题就在于低位拓展为高位这里,如果将第四个数改成10000000,因为计算机内存采用的是二进制的补码,为了保证拓展后的原码的值一致,最高位为1的数高位都是1而不是0,所以拓展后的数为111111111111111111111111100000000。但对于sm3算法来说数据就被修改了,正确的应该是00000000000000000000000010000000,因为sm3算法中并不会关心这个数原码的值是什么,而只关注它的二进制机器数。&0xff可以保证一个数从低位拓展为高位之后,其机器值不会发生改变。字符串编码的问题:在前面我们提到过这个程序用字符串来存储数据用于并进行填充与分组,但用字符串存储会因为字符串编码产生一些问题,为什么在这一章节讲,因为StringToIntArray方法中调用的getBytes()方法有一个字符编码的过程,而程序字符串编码产生的问题会在这里展现出来。在这里涉及到一些知识:char类型在java中占用多少个字节,其实与字符是中英文还有使用的编码集有关,如utf-8编码中文占3个字节,英文占1个字节,在ISO-8859-1编码下字符都占用1字节,因此ISO-8859-1编码的字符较少,无法编码中文等许多其他字符。char类型和String类型在内存中使用编码是Unicode。编码和解码实质是字符和二进制数的转换。在上文中经常提到的用一个char类型存储8比特的数据,其实该char字符在内存中占用可能不是8比特,只是对该char字符进行编码后能得到一个8比特的值。在StringToIntArray()方法中,使用了“b=s.getBytes("ISO-8859-1");”来对字符串进行编码转换成byte数组。为什么使用“ISO-8859-1”编码,而不使用其他编码,这其实与填充时填充的字符有关,下面用一段代码进行说明: publicstaticvoidmain(String[]args){ //TODOAuto-generatedmethodstub Strings=""; s+=(char)0x80; byte[]b,b1; try{ b=s.getBytes("ISO-8859-1"); //s.getBytes()默认使用当前平台的字符编码 b1=s.getBytes(); System.out.println("b:"+b[0]+"\nb1:"+b1[0]); }catch(UnsupportedEncodingExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); }运行结果如图3-3:图3-3运行结果和填充过程类型,向字符串中填入0x80,其二进制的值为二进制为10000000,通过运行结果发现与使用“ISO-8859-1”编码的输出b的二进制符合,但使用平台默认编码的输入b1不同,63(十进制)的二进制为00111111。其中的原因就是0x80在内存中解码成unicode字符,然后通过s.getBytes()编码时同一个字符被不同的编码集编码出了不同的二进制数。使用“ISO-8859-1”编码为了防止这种情况出现,但也会因此产生其他缺点,就如第1个知识点所说的,“ISO-8859-1”编码是单字节编码,其能编码的字符数相比其他编码方式要少得多,对于不能识别的字符,编码都会将改为‘?’字符。因此该程序只能对英文数字等字符进行sm3算法加密。3.4迭代压缩函数这里与sm3算法的伪代码部分基本一致,将分组后的消息迭代放入压缩函数CF中执行。//sm3迭代压缩函数 privatestaticint[]diedaiyasuo(int[][]m3){ int[]v={0x7380166f,0x4914b2b9,0x172442d7,0xda8a0600,0xa96f30bc,0x163138aa,0xe38dee4d,0xb0fb0e4e}; for(inti=0;i<m3.length;i++){ v=CF(v,m3[i]); } returnv; }消息拓展函数:该函数的作用是将消息分组拓展成生成132个消息字w0,w1,…w67,w’1,…w’63,,用于压缩函数CF中,这里消息字的定义是32比特的串,因此用java的int类型进行存储,其中w0,w1,…w67存储在w1数组中,w’1,…w’63存储在w2数组中。之后再将w1,w2两个数组打包成一个二维数组返回。//sm3消息拓展 privatestaticint[][]tuozhan(int[]tzm){ int[]w1=newint[68]; int[]w2=newint[64]; //前16位存储的是信息m for(inti=0;i<16;i++){ w1[i]=tzm[15-i]; } //后52位是拓展 for(inti=16;i<68;i++){ w1[i]=p1(w1[i-16]^w1[i-9]^left(w1[i-3],15))^left(w1[i-13],7)^w1[i-6]; } for(inti=0;i<64;i++){ w2[i]=w1[i]^w1[i+4]; } //将拓展出来的两个数组w1,w2用一个二维数组打包起来返回 int[][]w={w1,w2}; returnw; }压缩函数CF压缩函数CF中,八个字寄存器A,B,C,D,E,F,G,H,与中间变量ss1,ss2,tt1,tt2里的值都是32比特,因此都使用int类型进行存储。//压缩函数CF privatestaticint[]CF(int[]v,int[]b){ //常量tj intTj; //调用拓展函数 int[][]w=tuozhan(b); //定义各个寄存器 intA=v[0]; intB=v[1]; intC=v[2]; intD=v[3]; intE=v[4]; intF=v[5]; intG=v[6]; intH=v[7]; intss1; intss2; inttt1; inttt2; for(inti=0;i<64;i++){ if(i>=0&&i<=15){ Tj=0x79cc4519; } else{ Tj=0x7a879d8a; } ss1=left(left(A,12)+E+left(Tj,i),7); ss2=ss1^left(A,12); tt1=FFj(A,B,C,i)+D+ss2+w[1][i]; tt2=GGj(E,F,G,i)+H+ss1+w[0][i]; D=C; C=left(B,9); B=A; A=tt1; H=G; G=left(F,19); F=E; E=p(tt2); } //得到新的v int[]v1={A,B,C,D,E,F,G,H}; for(inti=0;i<8;i++){ v1[i]=v1[i]^v[i]; } returnv1; }布尔函数FFj与GGj:用于压缩函数CF中,代码如下://布尔函数privatestaticintFFj(intx,inty,intz,inti){ if(i>=0&&i<=15){ returnx^y^z; } else{ return((x&y)|(x&z)|(y&z)); } }privatestaticintGGj(intx,inty,intz,inti){ if(i>=0&&i<=15){ returnx^y^z; } else{ return((x&y)|(~x&z)); } }置换函数p,p1:p用于压缩函数CF中,p1用于消息拓展中,代码如下://sm3消息拓展中用到的置换函数p1 privatestaticintp1(intx){ returnx^left(x,15)^left(x,23); } //sm3压缩函数中用到的置换函数p privatestaticintp(intx){ returnx^left(x,9)^left(x,17); }循环左移函数left():在压缩函数CF中,需要用到循环左移的操作,即在左移的时候,超出左端的数后移动到最右端,如00001000,进行5次8比特位的循环左移的结果位00000001。由于java只提供了位移操作符只有左移(<<),右移(>>)与无符号右移(>>>),并没有提供循环位移,因此要自己实现。思路就是将左移后会在左端溢出的值通过无符号右移位移到最右端。还是上面的例子,00001000进行5次8比特位的循环左移,等于将其左移5次:00000000,与无符号右移3次:00000001,进行与操作。代码如下://32位循环左移 privatestaticintleft(inta,intb){ returna<<b|a>>>(32–b%32); }3.5程序main方法main方法是java程序的入口,我们需要在main方法内接收需要进行处理的字符串,调用sm3()方法进行SM3加密,并输出加密结果。代码如下: publicstaticvoidmain(String[]args){ //TODOAuto-generatedmethodstub System.out.println("输入需要加密的数据:\n"); Scannerinput=newScanner(System.in); Stringm=input.nextLine(); //m=sm3two(m); m=sm3(m); System.out.println(m);}程序运行结果如图3-4图3-4运行结果3.6程序存在的问题与改进在对本文中的程序完成后,我也回过头整个对程序进行审查,收集在设计与开发过程中的不足与问题。目前程序存在的问题主要有以下两点:1.SM3算法在设计上是能处理最多长度为264-1比特的消息,但在该程序的开发中,许多地方由于java数据类型的限制(如java数组最多只能存储232个元素),并不能对264-1比特的消息进行处理,目前程序只能保证处理长度在232比特以下的消息。2.也就是本文在分组函数一节中提到过该程序存在的问题:无法对“ISO-8859-1”编码集外的字符进行sm3加密。对于第一个问题,需要对程序多个地方的数据存储类型进行修改,根据算法分析出其所需要承受的数据量,再寻找更为合适的java数据结构进行存储,本文目前没有对该问题的解决方案。对于第二个问题,想要解决,就得从根源出发:不使用字符串在填充分组过程中存储消息。因此,本文也探索了一些其他的方法来实现这些功能,使用BigInteger来存储消息。BigInteger是java一个类,它的实例用于存储任意精度的整型,可以理解为没有长度限制的int类型,BigInteger自带许多用于数学计算的方法,几乎java整型能够进行的所有数学计算,BigInteger也都能够进行。使用BigInteger实现填充函数padding()://sm3填充函数2 privatestaticBigIntegerpadding2(Stringm){ BigIntegerm2=newBigInteger(m.getBytes()); //信息m的长度,sm3能处理消息长度最长为2的64次方bit,因此需要用long类型存储 longmLen=m.length()*8; //f是需要填充的0的个数 intf=(int)(512-(mLen+1+64)%512); //用位移填充:左移1位,填充1,再左移f+64位,填充信息长度mlen m2=m2.shiftLeft(1).add(BigInteger.ONE).shiftLeft(f+64).add(BigInteger.valueOf(mLen)); returnm2; }先把输入的消息m从字符串转换成BigInteger类型,再用BigInteger的位移函数实现消息填充,逻辑较为简单,左移1位,填充1,再左移f+64位,填充信息长度mlen(f是需要填充0的个数)。使用BigInteger实现分组函数: //sm3分组函数2 privatestaticint[][]fenzu2(BigIntegerm2){ //num:分组数,因java数组限制,分组数用32位的int存储 //bitLength()计算出的bit数不算符号位,所以要+1 intnum=(int)((m2.bitLength()+1)/512); //将bigInteger转换成int[][16],每个int[16]存储512bit, int[][]m3=newint[num][16]; //生成一个值为0xffffffff的BigInteger byte[]b={(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff}; BigIntegera=newBigInteger(b); //i:num-0 //j:0-16 for(inti=num;i>0;i--){ for(intj=0;j<16;j++){//与0x0xffffffff相与,得到后32位,并进行存储 m3[i-1][j]=m2.and(a).intValue(); //将原数右移32位,去掉已存储的部分。 m2=m2.shiftRight(32); } } returnm3; }将填充后的BigInteger分组。先生成一个值为0xffffffff的BigInteger,与存储消息的BigInteger相与,消息的后32位比特数,转换位int进行存储,循环16次后,得到存储512bit消息的int[16]。最终获得一个int类型的二维数组。两种方式优缺点:测试两种方式加密得到的哈希值,测试结果如表3.5.1,表3.5.2所示表3.5.1String方式输入256位杂凑值运行时间(纳秒)abc66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0730400nsefg7e0d11e6455eaabda193edefcbc8f7a5ae68193e5ec4d27aa2fad0b11c8e5245645300ns中心7fe10270de421704761d2d713296def351af99c1a7fd1861108bb73872a07b2440400ns一个7fe10270de421704761d2d713296def351af99c1a7fd1861108bb73872a07b2373300ns暴风雨c51238da672a707d2e6da79280634d0bac110d60eb5a40fbd99ffabcd7f8b7ed711800ns长颈鹿c51238da672a707d2e6da79280634d0bac110d60eb5a40fbd99ffabcd7f8b7ed384900nsabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd(去掉回车)debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732868100ns表3.5.2Biginteger方式输入256位杂凑值运行时间(纳秒)abc66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0908800nsefg7e0d11e6455eaabda193edefcbc8f7a5ae68193e5ec4d27aa2fad0b11c8e5245968100ns中心3b453bf1b95ee032db763624ff0c5012bfc9d2d59d0b8a44aa3c2379834279e993100ns一个a06c32ca76ec69343ac0db93452e339d1746c68c6afe51e02ed28acff2dfc00e1413500ns暴风雨8a4c508c5496c59de17ba814ed86ff10cd80d286ada7cef895fd25691ccd9ba893400ns长颈鹿525b6b76c26e4eb7e899af154d3b19441e67351a94ac305a86f2c89e4621a87a968600nsabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd(去掉回车)debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c57321660800ns结论:由表可以发现,String方式确实会出现中文等字符全部被替换成了‘?’而导致加密错误,Biginteger虽然中英文都能够成功加密,但在单次加密中相比String方式加密效率更慢。与java内的其他哈希算法进行效率对比:Java中的可以通过MessageDigest类进行各种信息摘要算法(哈希算法)加密,包括MD5,SHA-256算法。本节把实现的javaSM3加密程序与MessageDigest类的MD5,SHA-256加密进行效率对比。对比方式:三个算法对字符串“abc”迭代加密1000次,即对abc进行加密,把得到的hash值作为字符串再次进行加密,循环1000次。将三者的运行时间作对比。以下是代码: //MessageDigest类的MD5加密 privatestaticStringmd5(Stringm){ Strings=""; try{ MessageDigestmd5hash=MessageDigest.getInstance("md5"); md5hash.update(m.getBytes()); //byte数组转16进制字符串 s=byteToHexString(md5hash.digest()); }catch(NoSuchAlgorithmExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } returns; } //MessageDigest类的SHA-256加密privatestaticStringSHA256(Stringm){ Strings=""; try{ MessageDigestmd5hash=MessageDigest.getInstance("SHA-256"); md5hash.update(m.getBytes()); s=byteToHexString(md5hash.digest()); }catch(NoSuchAlgorithmExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } returns; } //byte数组转16进制字符串 privatestaticStringbyteToHexString(byte[]m){ char[]hexChar={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; StringhexString=""; for(bytei:m){ hexString=hexString+hexChar[i>>>4&0xf]+hexChar[i&0xf]; } returnhexString; }md5()和SHA256()分别是调用MessageDigest类的MD5加密,SHA-256加密,加密的结果是byte[]类型的数组,byteToHexString()方法用于将byte数组转换为16进制字符串。//主方法 publicstaticvoidmain(String[]args){ //TODOAuto-generatedmethodstub longstartTime=System.nanoTime();//纳秒 Strings="abc"; for(inti=1;i<1000;i++){ s=md5(s); //s=SHA256(s); //s=sm3(s);//String //s=sm3two(s);//bigInteger } longendTime=System.nanoTime(); System.out.println("程序运行时间:"+(endTime-startTime)+"ns"); }迭代加密1000次,打印运行时间运行结果如图3-5:图3-5迭代加密运行结果结论:MessageDigest类的MD5、SHA-256与SM3-String方式的加密效率相近,而SM3-BigInteger方式虽然在上一节的比较中单次加密效率不及SM3-String方式,但连续加密多次效率却反而比前三者更优,这可能与java语言的底层原理有关系。第四章总结这本文中,我最终也完成了目标,成功将SM3算法使用java实现。在这个过程中,我也遇到了许多困难。最开始接触到SM3算法的时候,由于本人数学功底一般,看着算法中各种字母,公式,符号,直感觉晕头转向。为了完全理解SM3算法的过程,我也是恶补了一下自己的数学知识,同时,通过网上查找到的c语言代码,一边学习c语言的同时一边通过代码理解SM3算法的过程,同时学习其编程思路来为即将进行的java程序设计做准备。在对SM3算法java程序开发中,也遇到过不少难题,比如遇到一些之前没有使用过的java类,这使得我必须通过反复查阅java文档,并编写测试程序来理解这些方法和类的作用。还有一些对内存的操作,由于java没有提供对应的方法或接口,需要自己实现。这次的论文课题,也是对我数学理解能力和java编程能力的一次锻炼。在“3.5程序存在的问题与改进”章节中提到的String和BigInteger两种实现方式。在设计最开始对“使用什么数据类型存储消息?”这个问题无从下手时,我通过java文档查找到了这个类,发现能满足我的需求,于是,就设计出了用BigInteger存储的方式。事实上BigInteger方式才是我最先开发出来的方式,后来我再通过网上查阅资料,也发现一些其他的SM3算法java实现,通过比较,我发现BigInteger方式的运行速度确实太慢,于是,我学习了他人的思路,在原来的代码架构中进行修改,这才有了String方式。当然之后我也发现这种方式依然存在缺点,于是将两个版本的代码都放入文中。既然两种方式都有其缺点,那有没有更好的方式来实现?这个问题我也有思考过,比如使用byte[]数组或java集合类,又或者自己写一个数据结构。但由于本人的编程能力有限,这些想法也许会在以后我有更加深厚的功底后来实现。参考文献[1]梁勇,戴开宇,Java语言程序设计与数据结构[M],机械工业出版社,2018.8[2]邓建球等,基于改进国密算法与区块链的数据登记系统[J],兵器装备工程学报,2019.9[3]陈小松,密码学及信息安全基础[M].清华大学出版社.2018.10[4]周明华等,国密算法JSSE密码套件的设计与实现[J],网络安全技术与应用,2019.7[5]YeYUAN等,针对一种基于SM3算法的消息验证码的相关能量攻击(英文)[J],FrontiersofInformationTechnology&ElectronicEngineering,2019.7[6]赵宇亮等,国家商用密码算法综述,2016电力行业信息化年会论文集,2016.9[7]申延召,SM3密码杂凑算法分析[D],东华大学,2013.1[8]李芳芳,国产密码行业应用的初步研究和探索[J],金融科技时代,2016.9[9]尹文琪,基于国密算法的SSLVPN的设计与实现[D],西安电子科技大学,2015.12[10]田椒陵,SM3算法界面设计及安全性分析[J],网络空间安全,2014.5[11]张长泽,SM3算法在硬件加密模块中的实现与应用[J],信息通信,2019.9[12]姚键,国产商用密码算法研究及性能分析[J],计算机应用与软件,2019.6[13]赵军等,国产与国外常用杂凑算法的比较分析[J],网络新媒体技术,2018.9[14]刘丽敏等,商用密码算法国际标准提案研究[J],信息技术与标准化,2018.5[15]邹剑等,对缩减轮数SM3散列函数改进的原像与伪碰撞攻击[J],通信学报,2018.1致谢这次毕业设计与毕业论文能够顺利完成,我衷心的感谢我的论文指导老师陈小松教授,在课堂上传授了许多知识给我,私底下也是十分平易近人,总会耐心的解答我们的问题。在这次的毕业设计中,除了为我解答了许多学术上的难题,也在论文选题、查阅文献、以及后期论文的修改与格式调整上给予了我许多帮助。同时我也要感谢我的其他老师,同学,朋友和家人,他们陪伴我度过了大学的四年时光,在这临近毕业,即将步入社会的时期,也是我人生中的一个重要的关口,他们给了我许多心理上的安慰与动力,给了我向上的动力。再次对他们所有人致以真挚的感谢!!!附录SM3算法java程序完整代码java实现sm3算法完整代码:importjava.io.UnsupportedEncodingException;importjava.math.BigInteger;importjava.util.Scanner;publicclasssm3two{ //sm3核心方法,用String存储 privatestaticStringsm3(Stringm){ //调用填充函数 Stringm2=padding(m); //调用分组函数 int[][]m3=fenzu(m2); //调用压缩函数 int[]m4=diedaiyasuo(m3); Stringsm3hash=""; for(inti:m4){ sm3hash+=Integer.toHexString(i); } returnsm3hash; } //sm3主方法2,用bigInteger存储 privatestaticStringsm3two(Stringm){ //调用填充函数 BigIntegerm2=padding2(m); //调用分组函数 int[][]m3=fenzu2(m2); //调用压缩函数 int[]m4=diedaiyasuo(m3); Stringsm3hash=""; for(inti:m4){ sm3hash+=Integer.toHexString(i); } returnsm3hash; } //sm3填充函数2 privatestaticBigIntegerpadding2(Stringm){ BigIntegerm2=newBigInteger(m.getBytes()); //信息m的长度,sm3能处理消息长度最长为2的64次方bit,因此需要用long类型存储 longmLen=m.length()*8; //f是需要填充的0的个数 intf=(int)(512-(mLen+1+64)%512); //用位移填充:左移1位,填充1,再左移f+64位,填充信息长度mlen m2=m2.shiftLeft(1).add(BigInteger.ONE).shiftLeft(f+64).add(BigInteger.valueOf(mLen)); returnm2; } //sm3填充函数 privatestaticStringpadding(Stringm){ //信息m的长度 longmLen=m.length()*8; //f是需要填充的0的个数 intf=(int)(512-(mLen+1+64)%512); //以字符为单位填充1和0 //0x80等于一个1和7个0 m+=(char)0x80; for(inti=0;i<(f-7)/8;i++){ m+=(char)0x00; } m+=longToString(mLen); returnm; } //sm3分组函数 privatestaticint[][]fenzu2(BigIntegerm2){ //num:分组数,因java数组限制,分组数用32位的int存储 //bitLength()计算出的bit数不算符号位,所以要+1 intnum=(int)((m2.bitLength()+1)/512); //将bigInteger转换成int[][16],每个int[16]存储512bit, int[][]m3=newint[num][16]; //生成一个值为0xffffffff的BigInteger byte[]b={(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff}; BigIntegera=newBigInteger(b); //i:num-0 //j:0-16 for(inti=num;i>0;i--){ for(intj=0;j<16;j++){ //与0x0xffffffff相与,得到后32位,并进行存储 m3[i-1][j]=m2.and(a).intValue(); //用位移分组:右移32位,左移32位,可以将后32位置0,再拿原数相减,得到后32位的数据,存储 //m3[i-1][j]=m2.subtract(m2.shiftRight(32).shiftLeft(32)).intValue(); //将原数右移32位,去掉已存储的部分。 m2=m2.shiftRight(32); } } returnm3; } //分组函数 privatestaticint[][]fenzu(Stringm2){ //num:分组数,因java数组限制,分组数用32位的int存储 intnum=m2.length()/64; //将String转换成int[num][16],每个int[16]存储512bit, int[][]m3=newint[num][16]; for(inti=0;i<num;i++){ m3[i]=StringToIntArray(m2.substring(i*64,i*64+64)); } returnm3; } //sm3迭代压缩函数 privatestaticint[]diedaiyasuo(int[][]m3){ int[]v={0x7380166f,0x4914b2b9,0x172442d7,0xda8a0600,0xa96f30bc,0x163138aa,0xe38dee4d,0xb0fb0e4e}; for(inti=0;i<m3.length;i++){ v=CF(v,m3[i]); } returnv; } //sm3消息拓展 privatestaticint[][]tuozhan(int[]tzm){ int[]w1=newint[68]; int[]w2=newint[64]; //前16位存储的是信息m for(inti=0;i<16;i++){ w1[i]=tzm[15-i]; } //后52位是拓展 for(inti=16;i<68;i++){ w1[i]=p1(w1[i-16]^w1[i-9]^left(w1[i-3],15))^left(w1[i-13],7)^w1[i-6]; } for(inti=0;i<64;i++){ w2[i]=w1[i]^w1[i+4]; } //将拓展出来的两个数组w1,w2用一个二维数组打包起来返回 int[][]w={w1,w2}; returnw; } //sm3消息拓展中用到的置换函数p1 privatestaticintp1(intx){ returnx^left(x,15)^left(x,23); } //sm3压缩函数中用到的置换函数p privatestaticintp(intx){ returnx^left(x,9)^left(x,17); } //压缩函数CF privatestaticint[]CF(int[]v,int[]b){ //常量tj intTj; //调用拓展函数 int[][]w=tuozhan(b); //定义各个寄存器 intA=v[0]; intB=v[1]; intC=v[2]; intD=v[3]; intE=v[4]; intF=v[5]; intG=v[6]; intH=v[7]; intss1; intss2; inttt1; inttt2; for(inti=0;i<64;i++){ if(i>=0&&i<=15){ Tj=0x79cc4519; } else{ Tj=0x7a879d8a; } ss1=left(left(A,12)+E+left(Tj,i),7); ss2=ss1^left(A,12); tt1=FFj(A,B,C,i)+D+ss2+w[1][i]; tt2=GGj(E,F,G,i)+H+ss1+w[0][i]; D=C; C=left(B,9); B=A; A=tt1; H=G; G=left(F,19); F=E; E=p(tt2); } //得到新的v int[]v1={A,B,C,D,E,F,G,H}; for(inti=0;i<8;i++){ v1[i]=v1[i]^v[i]; } returnv1; } //32位循环左移 privatestaticintleft(inta,intb){ returna<<b|a>>>(32-b%32); } //布尔函数 privatestaticintFFj(intx,inty,intz,inti){ if(i>=0&&i<=15){ returnx^y^z; } else{ return((x&y)|(x&z)|(y&z)); } } privatestaticintGGj(intx,inty,intz,inti){ if(i>=0&&i<=15){ returnx^y^z; } else{ return((x&y)|(~x&z)); } } //long类型转String类型 privatestaticStringlongToString(longnum){ Strings=""; for(inti=7;i>=0;i--){ s+=(char)((num>>i*8)&0xff); } returns; } //将512bit的字符串转换为int[16]数组 privatestaticint[]StringToIntArray(Strings){ byte[]b; int[]a=newint[16]; try{ b=s.getBytes("ISO-8859-1"); for(inti=0;i<16;i++){ //byte[4]转int //&0xff是为了char变int时只取低八位,因为开头为1的byte数变int时高位会自动补1 a[15-i]=(int)((b[i*4]&0xff)<<24)|((b[i*4+1]&0xff)<<16)|((b[i*4+2]&0xff)<<8)|(b[i*4+3]&0xff); } }catch(UnsupportedEncodingExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } returna; } publicstaticvoidmain(String[]args){ //TODOAuto-generatedmethodstub System.out.println("输入需要加密的数据:\n"); Scannerinput=newScanner(System.in); Stringm=input.nextLine(); //m=sm3two(m); m=sm3(m); System.out.println(m); } //测试填充函数publicstaticvoidtextpadding(){ Stringm=""; //填充函数 Stringm2=padding(m); //打印填充结果的二进制 System.out.println("填充结果为:"); for(inti=0;i<m2.length();i++){ Stringm3=Integer.toBinaryString((int)(m2.charAt(i))); //由于java会自动略高位的0,所以手动补齐 while(8-m3.length()>0){ m3='0'+m3; } System.out.print(""+m3); if((i+1)%8==0){ System.out.println(); } }}}

教你如何保护电脑一、每天关机前要做的清洗:

双击“我的电脑”—

—右键点C盘——点“属性”——点“磁盘清理”——点“确定”——再点“是”——再点“确定”。清理过程中,您可看得到未经您许可(您可点“查看文件”看,就知道了)进来的“临时文件”被清除了,盘的空间多了。对D,E,F盘也要用这法进行。

二、随时要进行的清理

:

打开网页——点最上面一排里的“工具”——点“Internet选项”——再点中间的“Internet临时文件”中的“删除文件”——再在“删除所有脱机内容”前的方框里打上勾——再点“确定”——清完后又点“确定”。这样,可为打开网和空间提高速度。

三、一星期进行的盘的垃圾清理

:

点“开始”——用鼠标指着“所有程序”,再指着“附件”,再指着“系统工具”,再点“磁盘粹片整理程序”——点C盘,再点“碎片整理”(这需要很长时间,最好在您去吃饭和没用电脑时进行。清理中您可看到您的盘里的状况,可将清理前后对比一下)——在跳出“清理完成”后点“关闭”。按上述,对D,E,F盘分别进行清理。

电脑系统越来越慢,怎么删除临时文件啊

1.关闭"休眠"

方法:打开[控制面板]→[电源选项]→[休眠],把"启用休眠"前面的勾去掉

说明:休眠是系统长时间一种待机状态,使您在长时间离开电脑时保存操作状态,如果您不是经常开着电脑到别处去的话,那就把它关了吧!

☆立即节省:256M

2.关闭"系统还原"

方法:打开[控制面板]→[系统]→[系统还原],把"在所有驱动器上关闭系统还原'勾上

说明:系统还原是便于用户误操作或产生软件问题时的一种挽救手段,可以回复到误操作以前的状态.不建议初级用户使用.当然,它采用的是跟踪手段,需要记录大量信息,所消耗的资源也要很大的.

☆立即节省:数百M

(根据还原点的多少而不同)

您也可以在不关闭系统还原的前提下,相应的减少系统还原所占的磁盘空间,这只会减少可用还原点的数目,一般还原点有一两个就够了吧.

方法:...[系统还原]-选择一个"可用驱动器"-[设置]-调整"要使用的磁盘空间"

3.关闭"远程管理"

方法:打开[控制面板]→[系统]→[远程],把

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论