《国钥SM3的Java程序实现.doc》由会员分享,可在线阅读,更多相关《国钥SM3的Java程序实现.doc(37页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、摘 要SM3算法是我国自主研发的hash算法。本文将对SM3算法的加密原理及各个过程进行解析,并使用java语言设计程序实现SM3算法,将SM3算法的各个流程通过java函数进行实现,包括数据填充,分组和迭代压缩等。在这个过程中,通过SM3加密过程中存储数据所使用的java数据类型不同,设计出两种不同的SM3算法java实现:SM3-String方式,SM3-BigInteger方式。并对两种方式的优缺点进行分析。最后将设计出来的SM3加密程序与java语言自带的其他杂凑算法(MD5,SHA-256)实现进行对比,比较它们的运行效率。【关键词】hash算法;国钥SM3;java程序设计;Abs
2、tractSM3 algorithm is a hash algorithm independently developed in China. This paper will analyze the encryption principle and each process of Sm3 algorithm, and use java language to design programs to realize SM3 algorithm, and implement each process of Sm3 algorithm through Java functions, includin
3、g data filling, grouping and iterative compression. In this process, through the different Java data types used to store data in the SM3 encryption process, two different SM3 algorithm java implementations are designed: SM3 string mode and SM3 BigInteger mode. The advantages and disadvantages of the
4、 two methods are analyzed. Finally, the SM3 encryption program designed is compared with other hash algorithms (MD5, SHA-256) of Java language, and their running efficiency is compared. Key words hash algorithm; national key SM3; Java programming; 目 录目录第一章 绪论11.1 研究背景与意义11.2 本文的总体内容2第二章 SM3算法过程解析32.
5、1 SM3算法简述32.2 SM3算法具体过程3第三章 SM3的java实现63.1 程序核心方法SM3()63.2 填充函数padding()63.3 分组函数fenzu()93.4 迭代压缩函数133.5 程序main方法173.6 程序存在的问题与改进18第四章 总结24参 考 文 献25致 谢26附 录27SM3算法java程序完整代码27广东东软学院本科生毕业设计(论文)第一章 绪论1.1 研究背景与意义哈希(Hash)算法,也叫散列函数或杂凑函数。它最大的特点是能将所有长度的信息转换成固定长度的哈希值,而且只能加密不能解密(只能单向运算)。是密码学里十分重要的分支,在数字签名、密码
6、协议、完整性认证、消息鉴别、等领域起到了十分巨大的作用。哈希算法的种类与发展:MD2:1989年, Ronald L. Rivest开发出了MD2算法。MD2算法把需要加密的消息,先进行填充,使消息的字节长度是16的倍数,然后在末尾添加一个16位的校验和,然后进行运算,得出128位散列值(哈希值)。MD4:1990年, Rivest继MD2后又开发了更安全的MD4算法,MD4算法对消息的填充方式与MD2不同,它使填充后消息长度 mod 512 =448,再将一个表示消息原来长度的64位二进制数填充到消息末尾。然后将消息按512比特一组进行分组,最后对每个分组进行三个不同步骤的处理,得出散列值(
7、哈希值)长度与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的算法只在压缩函数的讯息转换部分差了一个
8、位元的循环位移。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/22
9、4、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都是目前安全性较高哈希算法,越来越的人根据这些算法研究开发出各种软件和硬件,
10、并应用于各个行业和领域中。本文也是针对其中之一的国钥SM3算法进行编程实现。1.2 本文的总体内容本文主要使用java编程语言设计出能够进行SM3算法加密的程序,以下是本文的结构第一章:绪论,主要概述本论文相关技术的研究背景与意义。介绍文章结构第二章:对SM3算法的工作过程进行介绍与解析。第三章:SM3算法的java程序实现。第四章:对本文进行总结。第二章 SM3算法过程解析2.1 SM3算法简述SM3 杂凑算法可将长度小于 264 比特的消息 经过填充、反复的消息扩展和压缩,生成长度为 256 比特的杂凑值。2.2 SM3算法具体过程消息填充:其原理是在填充一个1,若干个0,和一个64位的消
11、息二进制长度,其中有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按下列方式迭代:FOR i=0 TO
12、 n-1 V(i+1) = CF( V(i) , B(i) )ENDFOR其中CF是压缩函数,v(0)为256bit初始值IV,值为0x7380166f,0x4914b2b9,0x172442d7,0xda8a0600,0xa96f30bc,0x163138aa,0xe38dee4d,0xb0fb0e4e B(i)为填充后的消息分组,迭代压缩的结果为V(n) 消息拓展:在上一步迭代压缩中,每一步的for循环我们都会将一个512比特的分组B(i)传入压缩函数CF中,在CF中B(i)会进行以下的操作来拓展生成132个字W0,W1,W67,W0,W1,W63,用于压缩函数CF的下一步操作:1.生成W
13、0-W16 :将消息分组B(i)的二进制每32位划分为一个字,最终划分出16个字,作为W0-W16。2.生成W16-W67 :FOR j=16 TO 67WjP1(Wj-16Wj-9Wj-315)(Wj-137)Wj-6ENDFOR3.生成W0-W63 :FOR j=0 TO 63 Wj=WjWj+4ENDFOR压缩函数CF:A,B,C,D,E,F,G,H为能够存储32为比特的字寄存器,SS1,SS2,TT1,TT2为中间变量,其中存储的也是32为比特,压缩函数Vi+1=CF(V(i),B(i),0in-1。计算过程如下:ABCDEFGHV(i)FOR j=0 TO 63SS1(A12)+E+
14、(Tjj)7SS2SS1(A12)TT1FFj(A,B,C)+D+SS2+WjTT2GGj(E,F,G)+H+SS1+WjDCCB9BAATT1HGGF19FEEP0(TT2)ENDFORV(i+1) ABCDEFGHVi杂凑值(哈希值):ABCDEFGH V(n)输出256比特的杂凑值y = ABCDEFGH。第三章 SM3的java实现3.1 程序核心方法SM3() /sm3核心方法,用String存储private static String sm3(String m) /调用填充函数String m2 = padding(m);/调用分组函数int m3 = fenzu(m2);/调用
15、压缩函数int m4 = diedaiyasuo(m3); String sm3hash = ;for(int i : m4) sm3hash+=Integer.toHexString(i);return sm3hash;sm3()方法就是整个sm3程序的主干,sm3算法的填充,分组,迭代压缩三个部分每部分都用一个独立的方法实现, sm3()核心方法的工作就是调用这些方法,之后将结果转换成16进制字符串的形式返回。sm3()方法使整个sm3算法的架构较为清晰,一定程度上降低了程序的耦合度。在测试阶段更容易定位问题的位置,日后若要对代码进行改进也更方便。3.2 填充函数padding()填充函数
16、的作用就是在消息后面填充一个1,若干个0,和一个64位的消息二进制长度,最终得出长度为512倍数的填充消息。整个填充过程,消息m都是用java的String字符串进行存储,一个String字符串由多个char字符组成,在填充时是以8比特的字符为单位对消息m进行填充/sm3填充函数private static String padding(String m) /mLen为信息m二进制的长度,sm3能处理消息长度最长为2的64次方bit,因此需要用java里长度为64位的long类型存储long mLen = m.length()*8;/f是需要填充的0的个数int f=(int)(512-(mL
17、en+1+64)%512);/以字符为单位填充1和0/0x80等于一个1和7个0m+=(char)0x80;for(int i = 0;i = 0;i-) s+=(char)(numi*8)&0xff);return s;测试填充结果:假设消息m为“usb”,其二进制为:01110101 01110011 01100010,长度mlen=24,需要填充0的个数f=512-24+1+64%512=423,因此填充的结果是:01110101 01110011 01100010 10000000 00000000 00000000 0000000 0000000000000000 00000000
18、00000000 00000000 00000000 00000000 0000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 0000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 0000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 0000000 0000000000000000 00000000 00000000 00000000 00
19、000000 00000000 0000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 0000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 0000000 00011000前文提到过,该程序的填充,分组,迭代压缩都用独立的函数实现,所以可以编写一段代码单独调用填充函数来测试结果是否正确/测试填充函数 public static void textpadding() String m = usb;/调用填充函数 String
20、m2 = padding(m); /打印填充结果的二进制 System.out.println(填充结果为:); for (int i = 0;i0) 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的倍数,填充后的消息二进制长度是
21、512的倍数,是8的倍数,最后填充的消息长度固定是64位,也是8的倍数。因此填充中间的1和若干个0的一定也是的二进制长度也必定为8的倍数,由此证明用字符为单位进行1和0的填充并不会出现不满或溢出的情况,如图 3-2所示:图 3-2 证明图解 3.3 分组函数fenzu()分组函数的作用是将填充后的消息按512比特为一组进行分组。在上一节填充里,填充后的消息是用String字符串进行存储的,根据一个字符8比特,分组后每一组都是一个长度为64的字符串。Java String类的substring()方法可以获取到字符串的子串,分组函数就是同过该方法每获取消息的64长度的子串,并进行存储,以此实现分
22、组的功能。为了方便后面迭代压缩的运算,设计出StringToIntArray()方法来将每组64长度的字符串转换成为一个长度为16的int数组。/分组函数private static int fenzu(String m2) /num:分组数,因java数组限制,分组数用32位的int存储int num = m2.length()/64;/将String转换成intnum16,每个int16存储512bit,int m3 = new intnum16;for(int i = 0;i num;i+) m3i = StringToIntArray(m2.substring(i*64,i*64+64
23、);return m3;stringToIntArray()方法:为了方便后面迭代压缩的运算,设计出StringToIntArray()方法来将每组64长度的字符串转换成为一个长度为16的int数组。/将512bit的字符串转换为int16数组private static int StringToIntArray(String s) byte b;int a = new int16;try b = s.getBytes(ISO-8859-1);for(int i = 0;i 16;i+)/byte4转int/& 0xff是为了char变int时只取低八位,因为开头为1的byte数变int时高位
24、会自动补1a15-i=(int)(bi*4 & 0xff) 24) | (bi*4+1 & 0xff) 16) | (bi*4+2 & 0xff) 8) | (bi*4+3 & 0xff); catch (UnsupportedEncodingException e) / TODO Auto-generated catch blocke.printStackTrace();return a;StringToIntArray()方法的思路是先用String类的getBytes()方法将String字符串转换为一个byte类型的数组,该数组每一个值都是一个8位的byte类型,而这里由于需要操作的字
25、符串已经固定为512比特,也就是能转换为16个int值。因此循环16次,每次循环将char数组里的4个字符转换为一个int值。4个byte类型转换为1个int类型的思路如下:例如某一次循环中,bj*4, bj*4+1, bj*4+2, bj*4+3在内存中的二进制值分别为01111111,00111111 ,00011111 ,00001111。将其分别左移24,16,8,0位得到:01111111 00000000 00000000 0000000000111111 00000000 00000000 00011111 00000000 00001111将4个数进行或(|)运算,得到一个32
26、位int类型的值:01111111001111110001111100001111“& 0xff”的作用:在进行或运算时,进行了一次“& 0xff”操作,0xff的值是11111111,理论上来所一个同样是8位的char类型的值对11111111进行&运算应该都为自己本身,那为什么还要进行这个操作?其实“& 0xff”操作修改的并不是低8位,而是高位。在上述4个数或运算的过程中,可以发现这4个数的位数是不一样的,分别是32位,24位,16位和8位,为了成功进行或运算,计算机会自动将低位扩展成高位,因此实际进行或运算的四个数是:01100001 00000000 00000000 0000000
27、000000000 01100010 00000000 0000000000000000 00000000 01100011 0000000000000000 00000000 00000000 01100100而问题就在于低位拓展为高位这里,如果将第四个数改成10000000,因为计算机内存采用的是二进制的补码,为了保证拓展后的原码的值一致,最高位为1的数高位都是1而不是0,所以拓展后的数为11111111 11111111 11111111 100000000。但对于sm3算法来说数据就被修改了,正确的应该是00000000 00000000 00000000 10000000,因为sm3
28、算法中并不会关心这个数原码的值是什么,而只关注它的二进制机器数。& 0xff可以保证一个数从低位拓展为高位之后,其机器值不会发生改变。字符串编码的问题:在前面我们提到过这个程序用字符串来存储数据用于并进行填充与分组,但用字符串存储会因为字符串编码产生一些问题,为什么在这一章节讲,因为StringToIntArray方法中调用的getBytes()方法有一个字符编码的过程,而程序字符串编码产生的问题会在这里展现出来。在这里涉及到一些知识:1. char类型在java中占用多少个字节,其实与字符是中英文还有使用的编码集有关,如utf-8编码中文占3个字节,英文占1个字节,在 ISO-8859-1编
29、码下字符都占用1字节,因此ISO-8859-1编码的字符较少,无法编码中文等许多其他字符。 2. char类型和String类型在内存中使用编码是Unicode。 3. 编码和解码实质是字符和二进制数的转换。 在上文中经常提到的用一个char类型存储8比特的数据,其实该 char字符在内存中占用可能不是8比特,只是对该char字符进行编码后能得到一个8比特的值。在StringToIntArray()方法中,使用了“b = s.getBytes(ISO-8859-1);”来对字符串进行编码转换成byte数组。为什么使用“ISO-8859-1”编码,而不使用其他编码,这其实与填充时填充的字符有关,
30、下面用一段代码进行说明:public static void main(String args) / TODO Auto-generated method stubString s = ;s+=(char)0x80;byte b,b1;try b = s.getBytes(ISO-8859-1);/s.getBytes()默认使用当前平台的字符编码b1 = s.getBytes();System.out.println(b:+b0+nb1:+b10); catch (UnsupportedEncodingException e) / TODO Auto-generated catch bloc
31、ke.printStackTrace();运行结果如图 3-3:图 3-3 运行结果和填充过程类型,向字符串中填入0x80,其二进制的值为二进制为1000 0000,通过运行结果发现与使用“ISO-8859-1”编码的输出b的二进制符合,但使用平台默认编码的输入b1不同,63(十进制)的二进制为0011 1111。其中的原因就是0x80在内存中解码成unicode字符,然后通过s.getBytes()编码时同一个字符被不同的编码集编码出了不同的二进制数。使用“ISO-8859-1”编码为了防止这种情况出现,但也会因此产生其他缺点,就如第1个知识点所说的,“ISO-8859-1”编码是单字节编码
32、,其能编码的字符数相比其他编码方式要少得多,对于不能识别的字符,编码都会将改为?字符。因此该程序只能对英文数字等字符进行sm3算法加密。3.4 迭代压缩函数这里与sm3算法的伪代码部分基本一致,将分组后的消息迭代放入压缩函数CF中执行。/sm3迭代压缩函数private static int diedaiyasuo(int m3) int v = 0x7380166f,0x4914b2b9,0x172442d7,0xda8a0600,0xa96f30bc,0x163138aa,0xe38dee4d,0xb0fb0e4e;for(int i = 0;im3.length;i+) v = CF(v
33、,m3i);return v;消息拓展函数:该函数的作用是将消息分组拓展成生成132个消息字w0,w1,w67,w1,w63,,用于压缩函数CF中,这里消息字的定义是32比特的串,因此用java的int类型进行存储,其中w0,w1,w67存储在w1数组中,w1,w63存储在w2数组中。之后再将w1,w2两个数组打包成一个二维数组返回。/sm3消息拓展private static int tuozhan(int tzm) int w1 = new int68;int w2 = new int64;/前16位存储的是信息mfor(int i = 0;i16;i+) w1i=tzm15-i;/后52
34、位是拓展for(int i = 16;i68;i+) w1i=p1(w1i-16 w1i-9 left(w1i-3,15) left(w1i-13,7) w1i-6;for(int i = 0;i64;i+) w2i=w1iw1i+4;/将拓展出来的两个数组w1,w2用一个二维数组打包起来返回int w = w1,w2;return w;压缩函数CF压缩函数CF中,八个字寄存器A,B,C,D,E,F,G,H,与中间变量ss1,ss2,tt1,tt2里的值都是32比特,因此都使用int类型进行存储。/压缩函数CFprivate static int CF(int v,int b) /常量tjin
35、t Tj;/调用拓展函数intw=tuozhan(b);/定义各个寄存器int A = v0;int B = v1;int C = v2;int D = v3;int E = v4;int F = v5;int G = v6;int H = v7;int ss1;int ss2;int tt1;int tt2;for(int i = 0;i=0&i=15) Tj = 0x79cc4519;elseTj = 0x7a879d8a;ss1 = left(left(A,12) + E + left(Tj,i),7);ss2 = ss1 left(A,12);tt1 = FFj(A,B,C,i) +
36、D + ss2 + w1i;tt2 = GGj(E,F,G,i) + H + ss1 + w0i;D = C;C = left(B,9);B = A;A = tt1;H = G;G = left(F,19);F = E;E = p(tt2);/得到新的vint v1 = A,B,C,D,E,F,G,H;for(int i = 0;i= 0 & i = 0& i = 15) return x y z;else return (x & y) | (x & z) ;置换函数p,p1:p用于压缩函数CF中,p1用于消息拓展中,代码如下:/sm3消息拓展中用到的置换函数p1private static
37、int p1(int x) return x left(x,15) left(x,23);/sm3压缩函数中用到的置换函数pprivate static int p(int x) return x left(x,9) left(x,17);循环左移函数left():在压缩函数CF中,需要用到循环左移的操作,即在左移的时候,超出左端的数后移动到最右端,如0000 1000,进行5次8比特位的循环左移的结果位0000 0001。由于java只提供了位移操作符只有左移()与无符号右移(),并没有提供循环位移,因此要自己实现。思路就是将左移后会在左端溢出的值通过无符号右移位移到最右端。还是上面的例子,
38、0000 1000进行5次8比特位的循环左移,等于将其左移5次:0000 0000,与无符号右移3次:0000 0001,进行与操作。代码如下:/32位循环左移private static int left(int a,int b) return a (32 b % 32);3.5 程序main方法main方法是java程序的入口,我们需要在main方法内接收需要进行处理的字符串,调用sm3()方法进行SM3加密,并输出加密结果。代码如下:public static void main(String args) / TODO Auto-generated method stubSystem.o
39、ut.println(输入需要加密的数据:n);Scanner input = new Scanner(System.in);String m = 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是ja