信息摘要算法之二:SHA1算法分析及實現
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算法分析及實現