1. 程式人生 > >信息摘要算法之二:SHA1算法分析及實現

信息摘要算法之二:SHA1算法分析及實現

專家 臨時 總結 tro sha-1 即使 img md4 stand

SHA算法,即安全散列算法(Secure Hash Algorithm)是一種與MD5同源的數據加密算法,該算法經過加密專家多年來的發展和改進已日益完善,現在已成為公認的最安全的散列算法之一,並被廣泛使用。

1、概述

SHA算法能計算出一個數位信息所對應到的,長度固定的字串,又稱信息摘要。而且如果輸入信息有任何的不同,輸出的對應摘要不同的機率非常高。因此SHA算法也是FIPS所認證的五種安全雜湊算法之一。原因有兩點:一是由信息摘要反推原輸入信息,從計算理論上來說是極為困難的;二是,想要找到兩組不同的輸入信息發生信息摘要碰撞的幾率,從計算理論上來說是非常小的。任何對輸入信息的變動,都有很高的幾率導致的信息摘要大相徑庭。

SHA實際上是一系列算法的統稱,分別包括:SHA-1、SHA-224、SHA-256、SHA-384以及SHA-512。後面4中統稱為SHA-2,事實上SHA-224是SHA-256的縮減版,SHA-384是SHA-512的縮減版。各中SHA算法的數據比較如下表,其中的長度單位均為位:

類別

SHA-1

SHA-224

SHA-256

SHA-384

SHA-512

消息摘要長度

160

224

256

384

512

消息長度

小於264

小於264

小於264

小於2128

小於2128

分組長度

512

512

512

1024

1024

計算字長度

32

32

32

64

64

計算步驟數

80

64

64

80

80

SHA-1在許多安全協定中廣為使用,包括TLS和SSL、PGP、SSH、S/MIME和IPsec,曾被視為是MD5的後繼者。SHA1主要適用於數字簽名標準(Digital Signature Standard DSS)裏面定義的數字簽名算法(Digital Signature Algorithm DSA)。對於長度小於264位的消息,SHA1會產生一個160位的消息摘要。

2、基本原理

前面我們簡單的介紹了SHA算法族,接下來我們以SHA-1為例來分析其基本原理。SHA-1是一種數據加密算法,該算法的思想是接收一段明文,然後以一種不可逆的方式將它轉換成一段密文,也可以簡單的理解為輸入一串二進制碼,並把它們轉化為長度較短、位數固定的輸出序列即散列值,也稱為信息摘要或信息認證代碼的過程。

SHA-1算法輸入報文的最大長度不超過264位,產生的輸出是一個160位的報文摘要。輸入是按512 位的分組進行處理的。SHA-1是不可逆的、防沖突,並具有良好的雪崩效應。

一般來說SHA-1算法包括有如下的處理過程:

1)、對輸入信息進行處理

既然SHA-1算法是對給定的信息進行處理得到相應的摘要,那麽首先需要按算法的要求對信息進行處理。那麽如何處理呢?對輸入的信息按512位進行分組並進行填充。如何填充信息報文呢?其實即使填充報文後使其按512進行分組後,最後正好余448位。那填充什麽內容呢?就是先在報文後面加一個1,再加很多個0,直到長度滿足對512取模結果為448。到這裏可能有人會奇怪,為什麽非得是448呢?這是因為在最後會附加上一個64位的報文長度信息,而448+64正好是512。

2)、填充長度信息

前面已經說過了,最後會補充信息報文使其按512位分組後余448位,剩下的64位就是用來填寫報文的長度信息的。至次可能大家也明白了前面說過的報文長度不能超過264位了。填充長度值時要註意必須是低位字節優先。

3)信息分組處理

經過添加位數處理的明文,其長度正好為512位的整數倍,然後按512位的長度進行分組,可以得到一定數量的明文分組,我們用Y0,Y1,……YN-1表示這些明文分組。對於每一個明文分組,都要重復反復的處理,這些與MD5都是相同的。

而對於每個512位的明文分組,SHA1將其再分成16份更小的明文分組,稱為子明文分組,每個子明文分組為32位,我們且使用M[t](t= 0, 1,……15)來表示這16個子明文分組。然後需要將這16個子明文分組擴充到80個子明文分組,我們將其記為W[t](t= 0, 1,……79),擴充的具體方法是:當0≤t≤15時,Wt = Mt;當16≤t≤79時,Wt = ( Wt-3 ⊕ Wt-8⊕ Wt-14⊕ Wt-16) <<< 1,從而得到80個子明文分組。

4)初始化緩存

所謂初始化緩存就是為鏈接變量賦初值。前面我們實現MD5算法時,說過由於摘要是128位,以32位為計算單位,所以需要4個鏈接變量。同樣SHA-1采用160位的信息摘要,也以32位為計算長度,就需要5個鏈接變量。我們記為A、B、C、D、E。其初始賦值分別為:A = 0x67452301、B = 0xEFCDAB89、C = 0x98BADCFE、D = 0x10325476、E = 0xC3D2E1F0。

如果我們對比前面說過的MD5算法就會發現,前4個鏈接變量的初始值是一樣的,因為它們本來就是同源的。

5)計算信息摘要

經過前面的準備,接下來就是計算信息摘要了。SHA1有4輪運算,每一輪包括20個步驟,一共80步,最終產生160位的信息摘要,這160位的摘要存放在5個32位的鏈接變量中。

在SHA1的4論運算中,雖然進行的就具體操作函數不同,但邏輯過程卻是一致的。首先,定義5個變量,假設為H0、H1、H2、H3、H4,對其分別進行如下操作:

(A)、將A左移5為與 函數的結果求和,再與對應的子明文分組、E以及計算常數求和後的結果賦予H0。

(B)、將A的值賦予H1。

(C)、將B左移30位,並賦予H2。

(D)、將C的值賦予H3。

(E)、將D的值賦予H4。

(F)、最後將H0、H1、H2、H3、H4的值分別賦予A、B、C、D

這一過程表示如下:

技術分享圖片

而在4輪80步的計算中使用到的函數和固定常熟如下表所示:

計算輪次

計算的步數

計算函數

計算常數

第一輪

0≤t≤19步

ft(B,C,D)=(B&C)|(~B&D)

Kt=0x5A827999

第二輪

20≤t≤39步

ft(B,C,D)=B⊕C⊕D

Kt=0x6ED9EBA1

第三輪

40≤t≤59步

ft(B,C,D)=(B&C)|(B&D)|(C&D)

Kt=0x8F188CDC

第四輪

60≤t≤79步

ft(B,C,D)=B⊕C⊕D

Kt=0xCA62C1D6

經過4論80步計算後得到的結果,再與各鏈接變量的初始值求和,就得到了我們最終的信息摘要。而對於有多個銘文分組的,則將前面所得到的結果作為初始值進行下一明文分組的計算,最終計算全部的明文分組就得到了最終的結果。

3、軟件實現

經過前面的分析過程,接下來要具體實現SHA1算法其實已經很簡單了!下面來一步步實現它,首先實現初始化函數:

 1 /*  SHA1Reset函數用於初始化SHA1的內容值                          */
 2 /*  參數:context,SHA的內容值,存儲計算結果既初始值,輸入輸出      */
 3 /*  返回值:SHA錯誤代碼                                            */
 4 ErrorCode SHA1Reset(SHA1Context *context)
 5 {
 6   if (!context)
 7   {
 8     return shaNull;
 9     }
10  
11   context->Length_Low             = 0;
12   context->Length_High            = 0;
13   context->Message_Block_Index    = 0;
14  
15   context->Intermediate_Hash[0]   = 0x67452301;
16   context->Intermediate_Hash[1]   = 0xEFCDAB89;
17   context->Intermediate_Hash[2]   = 0x98BADCFE;
18   context->Intermediate_Hash[3]   = 0x10325476;
19   context->Intermediate_Hash[4]   = 0xC3D2E1F0;
20  
21   context->Computed   = 0;
22   context->Corrupted  = 0;
23  
24   return shaSuccess;
25 }

接下來實現明文信息的讀取及處理函數,該函數讀取信息分組,並輸入前次計算的結果,對除最後一組信息外的全部分組進行信息摘要計算。

 1 /*  SHA1Input函數,將分組的信息讀入並進行摘要計算                    */
 2 /*  參數:                                                                             */
 3 /*      context,SHA的內容值,存儲計算結果既初始值,輸入輸出     */
 4 /*      message_array,待處理的信息分組的字節數組,輸入參數     */
 5 /*      length,message_array數組中信息的長度                        */
 6 /*  返回值:SHA錯誤代碼                                                       */
 7 ErrorCode SHA1Input(SHA1Context *context,const uint8_t *message_array,unsigned length)
 8 {
 9   if (!length)
10   {
11     return shaSuccess;
12   }
13  
14   if (!context || !message_array)
15   {
16     return shaNull;
17   }
18  
19   if (context->Computed)
20   {
21     context->Corrupted = shaStateError;
22  
23     return shaStateError;
24   }
25  
26   if (context->Corrupted)
27   {
28     return (ErrorCode)context->Corrupted;
29   }
30   while(length-- && !context->Corrupted)
31   {
32     context->Message_Block[context->Message_Block_Index++] =
33 
34                     (*message_array & 0xFF);
35  
36     context->Length_Low += 8;
37     if (context->Length_Low == 0)
38     {
39       context->Length_High++;
40       if (context->Length_High == 0)
41       {
42         /* 消息長度超過限值 */
43         context->Corrupted = 1;
44       }
45     }
46  
47     if (context->Message_Block_Index == 64)
48     {
49         SHA1ProcessMessageBlock(context);
50     }
51  
52     message_array++;
53   }
54  
55   return shaSuccess;
56 }

然後實現結果輸出函數,該函數對信息的最後部分進行處理並返回160位的信息摘要到Message_Digest數組,該數組作為參數有調用者輸入。第一個元素存第一個字節,依次20個字節。

 1 /*  SHA1Result函數,對信息的最後部分進行處理並輸出最終計算結果 */
 2 /*  參數:                                                                             */
 3 /*      context,SHA的內容值,存儲計算結果既初始值,輸入輸出     */
 4 /*      Message_Digest,信息摘要的返回值,輸出參數                  */
 5 /*  返回值:SHA錯誤代碼                                                       */
 6 ErrorCode SHA1Result( SHA1Context *context,uint8_t Message_Digest[SHA1HashSize])
 7 {
 8   int i;
 9  
10   if (!context || !Message_Digest)
11   {
12     return shaNull;
13   }
14  
15   if (context->Corrupted)
16   {
17     return (ErrorCode)context->Corrupted;
18   }
19  
20   if (!context->Computed)
21   {
22     SHA1PadMessage(context);
23     for(i=0; i<64; ++i)
24     {
25       /* 處理完畢,清除消息分組 */
26       context->Message_Block[i] = 0;
27     }
28     context->Length_Low = 0;    /* 清除長度數據 */
29     context->Length_High = 0;
30     context->Computed = 1;
31   }
32  
33   for(i = 0; i < SHA1HashSize; ++i)
34   {
35     Message_Digest[i] = context->Intermediate_Hash[i>>2]>>8*(3-(i&0x03));
36   }
37  
38   return shaSuccess;
39 }

還需要實現消息分組的處理函數。該函數處理存儲於Message_Block數組中的,待處理的512位的明文分組,將其處理為80個子明文分組,並進行4輪80步運算,返回相應的摘要值。

 1 /*  SHA1ProcessMessageBlock函數,處理消息分組                    */
 2 /*  描述:                                                       */
 3 /*  參數:                                                       */
 4 /*      context,SHA的內容值,存儲計算結果既初始值,輸入輸出     */
 5 /*  返回值:無                                                   */
 6 static void SHA1ProcessMessageBlock(SHA1Context *context)
 7 {
 8   /* SHA-1計算中用到的常數定義 */
 9   const uint32_t K[]={0x5A827999,0x6ED9EBA1,0x8F1BBCDC,0xCA62C1D6};
10  
11   int t;                         /* 循環變量 */
12   uint32_t temp;                /* 臨時變量,存計算值 */
13   uint32_t W[80];               /* 子明文分組數組 */
14   uint32_t A, B, C, D, E;       /* 初始值緩存變量 */
15  
16   /* 初始化子明文分組W的前16個字 */
17   for(t = 0; t < 16; t++)
18   {
19     W[t] = context->Message_Block[t * 4] << 24;
20     W[t] |= context->Message_Block[t * 4 + 1] << 16;
21     W[t] |= context->Message_Block[t * 4 + 2] << 8;
22     W[t] |= context->Message_Block[t * 4 + 3];
23   }
24  
25   for(t = 16; t < 80; t++)
26   {
27     W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
28   }
29  
30   A = context->Intermediate_Hash[0];
31   B = context->Intermediate_Hash[1];
32   C = context->Intermediate_Hash[2];
33   D = context->Intermediate_Hash[3];
34   E = context->Intermediate_Hash[4];
35  
36   /*第1輪20步計算*/
37   for(t = 0; t < 20; t++)
38   {
39     temp =  SHA1CircularShift(5,A)+((B & C) | ((~B) & D)) + E + W[t] + K[0];
40     E = D;
41     D = C;
42     C = SHA1CircularShift(30,B);
43     B = A;
44     A = temp;
45     }
46  
47   /*第2輪20步計算*/
48   for(t = 20; t < 40; t++)
49   {
50     temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
51     E = D;
52     D = C;
53     C = SHA1CircularShift(30,B);
54     B = A;
55     A = temp;
56   }
57  
58   /*第3輪20步計算*/
59   for(t = 40; t < 60; t++)
60   {
61     temp = SHA1CircularShift(5,A)+((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
62     E = D;
63     D = C;
64     C = SHA1CircularShift(30,B);
65     B = A;
66     A = temp;
67   }
68  
69   /*第4輪20步計算*/
70   for(t = 60; t < 80; t++)
71   {
72     temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
73     E = D;
74     D = C;
75     C = SHA1CircularShift(30,B);
76     B = A;
77     A = temp;
78   }
79  
80   context->Intermediate_Hash[0] += A;
81   context->Intermediate_Hash[1] += B;
82   context->Intermediate_Hash[2] += C;
83   context->Intermediate_Hash[3] += D;
84   context->Intermediate_Hash[4] += E;
85  
86   context->Message_Block_Index = 0;
87 }

還需要實現一個對消息進行補位和追加消息長度並進行處理的函數。根據標準,消息必須被填充到一個剩至512位。第一個填充位必須是‘1‘。最後64位表示原始消息的長度。中間的所有位都應該是0。該函數將根據這些規則填充消息,並相應地填充Message_Block數組。

 1 /*  SHA1PadMessage函數,補全消息,並添加長度,計算最終結果      */
 2 /*  參數:                                                       */
 3 /*      context,SHA的內容值,存儲計算結果既初始值,輸入輸出     */
 4 /*  返回值:無                                                   */
 5 static void SHA1PadMessage(SHA1Context *context)
 6 {
 7   /* 檢查當前的消息分組,如果小於等於55個字節,則直接添加補位和長度信息。否則,如果大於55個字節,我們填充塊到512位,並處理它,然後繼續填充到第二個塊中直道448位,最後填寫長度信息。*/
 8   if (context->Message_Block_Index > 55)
 9   {
10     context->Message_Block[context->Message_Block_Index++] = 0x80;
11     while(context->Message_Block_Index < 64)
12     {
13       context->Message_Block[context->Message_Block_Index++] = 0;
14     }
15  
16     SHA1ProcessMessageBlock(context);
17  
18     while(context->Message_Block_Index < 56)
19     {
20       context->Message_Block[context->Message_Block_Index++] = 0;
21     }
22   }
23   else
24   {
25     context->Message_Block[context->Message_Block_Index++] = 0x80;
26     while(context->Message_Block_Index < 56)
27     {
28       context->Message_Block[context->Message_Block_Index++] = 0;
29     }
30   }
31  
32   /* 將明文長度填入到最後8個字節中 */
33   context->Message_Block[56] = context->Length_High >> 24;
34   context->Message_Block[57] = context->Length_High >> 16;
35   context->Message_Block[58] = context->Length_High >> 8;
36   context->Message_Block[59] = context->Length_High;
37   context->Message_Block[60] = context->Length_Low >> 24;
38   context->Message_Block[61] = context->Length_Low >> 16;
39   context->Message_Block[62] = context->Length_Low >> 8;
40   context->Message_Block[63] = context->Length_Low;
41  
42   SHA1ProcessMessageBlock(context);
43 }

至此SHA1散列算法就全部實現完了,需要說明一下的是相應的結構體定義和錯誤代碼的定義如下:

 1 /*  定義SHA-1內容保存結構體  */
 2 typedef struct SHA1Context
 3 {
 4     uint32_t Intermediate_Hash[SHA1HashSize/4];         /* 信息摘要 */
 5  
 6     uint32_t Length_Low;                                /* 按位計算的信息長度低字 */
 7     uint32_t Length_High;                               /* 按位計算的信息長度高字 */
 8  
 9     int_least16_t Message_Block_Index;                  /* 信息分組數組的索引 */
10     uint8_t Message_Block[64];                          /* 512位信息分組 */
11  
12     int Computed;                                       /* 摘要計算標識 */
13     int Corrupted;                                      /* 信息摘要損壞標識 */
14 } SHA1Context;
15  
16 typedef enum
17 {
18     shaSuccess = 0,     /* 處理成功 */
19     shaNull,            /* 指針參數為NUll */
20     shaInputTooLong,    /* 輸入消息長度超範圍 */
21     shaStateError       /* 在處理完畢後,未經初始化直接調用輸入處理 */
22 }ErrorCode;

4、總結

我們已經實現了SHA1這一散列算法,接下來我們驗證一下它的效果如何。首先我們輸入信息“abcdef”,計算結果,並使用通用工具驗算。

技術分享圖片技術分享圖片

以上2圖我們可以看到結果是一致的,接下來我們輸入信息:“a1b23c4d5e6f7g8h9i0j”,計算結果如下:

技術分享圖片技術分享圖片

對比上述2圖的結果也是一致的。接下來我們分別測試長度448位、長度超過448位、長度超過512位的明文信息,所得的結果也是正確的,說明我們的實現沒有問題。

前面我說了SHA-1與MD5是同源的散列算法,那他們究竟有何區別於聯系呢?接下來我們簡單的比較一下這兩種算法:

(1)、因為二者均由MD4導出,SHA-1和MD5彼此很相似。相應的,他們的強度和其他特性也是相似,但還有以下幾點不同:

(2)、對強行供給的安全性:最顯著和最重要的區別是SHA-1摘要比MD5摘要長32 位。使用強行技術,產生任何一個報文使其摘要等於給定報摘要的難度對MD5是2^128數量級的操作,而對SHA-1則是2^160數量級的操作。這樣,SHA-1對強行攻擊有更大的強度。

(3)、對密碼分析的安全性:由於MD5的設計,易受密碼分析的攻擊,SHA-1顯得不易受這樣的攻擊。

(4)、速度:在相同的硬件上,SHA-1的運行速度比MD5慢。

信息摘要算法之二:SHA1算法分析及實現