天天看點

資訊摘要算法之四:SHA512算法分析與實作

前面一篇中我們分析了SHA256的原理,并且實作了該算法,在這一篇中我們将進一步分析SHA512并實作之。

1、SHA簡述

盡管在前面的篇章中我們介紹過SHA算法,但出于闡述的完整性我依然要簡單的說明一下SHA算法。SHA主要有SHA-1、SHA-224、SHA-256、SHA-384以及SHA-512。各種SHA算法的資料比較如下表,其中的長度機關均為位:

資訊摘要算法之四:SHA512算法分析與實作

從上表中我們不難發現,SHA-224和SHA-256、SHA-384和SHA-512在消息長度、分組長度、計算字長以及計算步驟各方面分别都是一緻的。事實上通常認為SHA-224是SHA-256的縮減版,而SHA-384是SHA-512的縮減版。在前面的篇章中,我們已經實作了SHA-224和SHA-256,在這一篇中我們将讨論SHA-384和SHA-512算法并實作之。

2、消息的填充與解析

在這裡我們讨論的散列函數用于在計算機中将根據作為輸入消息或者資料檔案生成其對應的資訊摘要。消息或資料檔案通常被作為是位字元串。消息的長度是消息中的比特數(空消息的長度為0)。如果消息中的比特數是8的倍數,那麼我們就可以用十六進制來表示消息的緊湊性。消息填充的目的是為了在消息填充後,在SHA-384和SHA-512中消息的長度是1024位的整數倍。

接下來我們說明消息或者資料檔案将如何實作填充。總的來說就是,先添加一個“1”,再後跟多個“0”,然後再追加一個128位的消息長度資訊,使得填充完成後的消息長度正好是1024位的整數倍。追加的128位的消息長度資訊是原始消息的位長,填充完成的消息會被分成1024位的消息分組。

對于SHA-384和SHA-512來說消息的最大長度L<2128,在對消息進行散列運算之前也需要對消息做相應的填充處理。SHA-384和SHA-512的消息填充過程與SHA-224和SHA-256的填充過程是基本一緻的。

首先,也是在原始消息後填充一個“1”,例如:如果原始資訊是"01010000",完成這一填充之後就是 "010100001"。

接下來,填充K個“0”,所不同的是消息分組的長度是1024位,是以K的取值必須是滿足下述表達式的最小非負整數值。

( L + 1 + K ) mod 1024 = 896

最後,在填充完必的消息後,追加128位的原始消息長度,因為消息的長度不會超過2128位,是以其長度資料的值不會超過128位,這也是為什麼上一步中需要取模餘896的原因。填充完畢後,所有的消息分組都将是一個1024位。

3、疊代函數與常數

SHA算法這類雜湊演算法的計算過程都需要用到邏輯函數和計算常量。但由于具體算法的不同所使用的具體的函數和常數略有差别。我們在前面的篇章中說過MD5和SHA1,它們都有4個邏輯函數,而在SHA2的一系列算法中都采用了6個邏輯函數。接下來将說明這些邏輯函數和常量。

SHA-384和SHA-512也都有6個疊代函數,但是每個函數操作64位的輸入字(x、y、z),輸出也是一個新的 64 位字。這些邏輯函數表示如下:

CH( x, y, z) = (x AND y) XOR ( (NOT x) AND z)

MAJ( x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z)

BSIG0(x) = ROTR^28(x) XOR ROTR^34(x) XOR ROTR^39(x)

BSIG1(x) = ROTR^14(x) XOR ROTR^18(x) XOR ROTR^41(x)

SSIG0(x) = ROTR^1(x) XOR ROTR^8(x) XOR SHR^7(x)

SSIG1(x) = ROTR^19(x) XOR ROTR^61(x) XOR SHR^6(x)

SHA-384和SHA-512采用相同的,80個64位的常數序列。通常記為:K0、K1、……、K79,這些常數的取值是前80個質數的立方根的小數部分的前64位。這些數以16進制表示如下:

428a2f98d728ae22 7137449123ef65cd b5c0fbcfec4d3b2f e9b5dba58189dbbc

3956c25bf348b538 59f111f1b605d019 923f82a4af194f9b ab1c5ed5da6d8118

d807aa98a3030242 12835b0145706fbe 243185be4ee4b28c 550c7dc3d5ffb4e2

72be5d74f27b896f 80deb1fe3b1696b1 9bdc06a725c71235 c19bf174cf692694

e49b69c19ef14ad2 efbe4786384f25e3 0fc19dc68b8cd5b5 240ca1cc77ac9c65

2de92c6f592b0275 4a7484aa6ea6e483 5cb0a9dcbd41fbd4 76f988da831153b5

983e5152ee66dfab a831c66d2db43210 b00327c898fb213f bf597fc7beef0ee4

c6e00bf33da88fc2 d5a79147930aa725 06ca6351e003826f 142929670a0e6e70

27b70a8546d22ffc 2e1b21385c26c926 4d2c6dfc5ac42aed 53380d139d95b3df

650a73548baf63de 766a0abb3c77b2a8 81c2c92e47edaee6 92722c851482353b

a2bfe8a14cf10364 a81a664bbc423001 c24b8b70d0f89791 c76c51a30654be30

d192e819d6ef5218 d69906245565a910 f40e35855771202a 106aa07032bbd1b8

19a4c116b8d2d0c8 1e376c085141ab53 2748774cdf8eeb99 34b0bcb5e19b48a8

391c0cb3c5c95a63 4ed8aa4ae3418acb 5b9cca4f7763e373 682e6ff3d6b2b8a3

748f82ee5defb2fc 78a5636f43172f60 84c87814a1f0ab72 8cc702081a6439ec

90befffa23631e28 a4506cebde82bde9 bef9a3f7b2c67915 c67178f2e372532b

ca273eceea26619c d186b8c721c0c207 eada7dd6cde0eb1e f57d4f7fee6ed178

06f067aa72176fba 0a637dc5a2c898a6 113f9804bef90dae 1b710b35131c471b

28db77f523047d84 32caab7b40c72493 3c9ebe0a15c9bebc 431d67c49c100d4c

4cc5d4becb3e42b6 597f299cfc657e2a 5fcb6fab3ad6faec 6c44198c4a475817

4、計算過程

前面,我們已經介紹了消息的預處理及散列邏輯函數,接下來我們将說明摘要的計算過程。

每個安全散列函數的輸出,在應用到一個分為N個分組的消息後,結果記為散列量H(N)。對于SHA-384和SHA-512,它可以被認為是8個64位的字,記為:H(i)0、H(i)1、…、H(i)7。

散列字被初始化為一個特定的值,并在處理完每一個消息分組後對它進行更新,并在處理最後一個塊後将其連接配接起來以産生輸出。對于SHA-512,所有的H(N)變量都是串聯的,而SHA-384散列值是通過最後連接配接時,省略一些而産生的。

與前面類似,對于SHA-384,初始化散列值有下述8個64為的16進制數組成。這些數由第9到16個質數平方根的小數部分的前64位得到。

H(0)0 = cbbb9d5dc1059ed8

H(0)1 = 629a292a367cd507

H(0)2 = 9159015a3070dd17

H(0)3 = 152fecd8f70e5939

H(0)4 = 67332667ffc00b31

H(0)5 = 8eb44a8768581511

H(0)6 = db0c2e0d64f98fa7

H(0)7 = 47b5481dbefa4fa4

對于SHA-384,初始化散列值有下述8個64為的16進制數組成。這些數由前8個質數平方根的小數部分的前64位得到。

H(0)0 = 6a09e667f3bcc908

H(0)1 = bb67ae8584caa73b

H(0)2 = 3c6ef372fe94f82b

H(0)3 = a54ff53a5f1d36f1

H(0)4 = 510e527fade682d1

H(0)5 = 9b05688c2b3e6c1f

H(0)6 = 1f83d9abfb41bd6b

H(0)7 = 5be0cd19137e2179

SHA-384和SHA-512對消息分組執行相同的處理,隻在初始化H(0)和如何生成最終輸出的過程中有所不同。SHA-384和SHA-5126可以用來散列處理長度為L位的消息,其中0 < L< = 2128。算法使用一個80個64位字的消息清單, 8個工作變量64位以及8個64位字的散列值。

消息清單每32位分為一個子分組,被标記為W0、W1、…、W79。8個工作變量分别為a、b、c、d、e、f、g和h,8個散列值被标記為h(i)0、h(i)1、…、H(i)7,并保留初始散列值H(0),替換為每一個連續的中間散列值(在處理完每個消息分組後)H(i),并在處理完所有N塊後,以最終的散列值H(N)結束。

從前面我們知道,填充完了之後消息被分為了1024位的消息分組。每個分組被分為16個64位的子分組,記為:M(i)0、M(i)1、...、M(i)15。将對N個消息分組進行如下操作。

a、準備消息清單

For t = 0 to 15

Wt = M(i)t

For t = 16 to 79

Wt = SSIG1(W(t-2)) + W(t-7) + SSIG0(W(t-15)) + W(t-16)

b、初始化工作變量

a = H(i-1)0

b = H(i-1)1

c = H(i-1)2

d = H(i-1)3

e = H(i-1)4

f = H(i-1)5

g = H(i-1)6

h = H(i-1)7

c、執行散列計算

For t = 0 to 79

T1 = h + BSIG1(e) + CH(e,f,g) + Kt + Wt

T2 = BSIG0(a) + MAJ(a,b,c)

h = g

g = f

f = e

e = d + T1

d = c

c = b

b = a

a = T1 + T2

d、計算最終結果

H(i)0 = a + H(i-1)0

H(i)1 = b + H(i-1)1

H(i)2 = c + H(i-1)2

H(i)3 = d + H(i-1)3

H(i)4 = e + H(i-1)4

H(i)5 = f + H(i-1)5

H(i)6 = g + H(i-1)6

H(i)7 = h + H(i-1)7

所有消息分組順序完成上述計算過程後,還會計算最終的輸出結果。對于SHA-12來說,,是所有H(N)0、H(N)1到H(N)7的串聯。對于SHA-384,則是H(N)0、H(N)1直到H(N)5的串聯。

5、代碼實作

前面我們已經說明了SHA-512(SHA-384)的計算過程,接下來我們将這一過程代碼化。同樣的首先定義一個上下文的結構。

1 /** 定義SHA-512哈希操作的内容資訊結構體 */
 2 typedef struct SHA512Context {
 3 #ifdef USE_32BIT_ONLY
 4 
 5   uint32_t Intermediate_Hash[SHA512HashSize/4]; /* 資訊摘要 */
 6   uint32_t Length[4];                           /* 按位計算的資訊摘要的長度 */
 7 
 8 #else /* !USE_32BIT_ONLY */
 9 
10   uint64_t Intermediate_Hash[SHA512HashSize/8]; /* 資訊摘要 */
11   uint64_t Length_High;                         /* 按位計算的資訊摘要的長度 */
12   uint64_t Length_Low;                          /* 按位計算的資訊摘要的長度 */
13 
14 #endif /* USE_32BIT_ONLY */
15 
16   int_least16_t Message_Block_Index;            /* 資訊分組數組的索引 */
17   uint8_t Message_Block[SHA512_Message_Block_Size];/* 1024位資訊分組 */
18   int Computed;                                 /* 摘要計算辨別*/
19   int Corrupted;                                /* 資訊摘要損壞辨別 */
20 } SHA512Context;      

接下來實作SHA512Context結構的初始化,為後續的計算過程做準備。

1 #ifdef USE_32BIT_ONLY
 2 static SHAStatusCode SHA384_512Reset(SHA512Context *context,uint32_t H0[SHA512HashSize/4])
 3 #else /* !USE_32BIT_ONLY */
 4 static SHAStatusCode SHA384_512Reset(SHA512Context *context,uint64_t H0[SHA512HashSize/8])
 5 #endif /* USE_32BIT_ONLY */
 6 {
 7   int i;
 8 
 9   if (!context) return shaNull;
10 
11   context->Message_Block_Index = 0;
12 
13 #ifdef USE_32BIT_ONLY
14 
15   context->Length[0] = context->Length[1] =
16   context->Length[2] = context->Length[3] = 0;
17   for (i = 0; i < SHA512HashSize/4; i++)
18     context->Intermediate_Hash[i] = H0[i];
19 
20 #else /* !USE_32BIT_ONLY */
21 
22   context->Length_High = context->Length_Low = 0;
23   for (i = 0; i < SHA512HashSize/8; i++)
24     context->Intermediate_Hash[i] = H0[i];
25 
26 #endif /* USE_32BIT_ONLY */
27 
28   context->Computed = 0;
29   context->Corrupted = shaSuccess;
30 
31   return shaSuccess;
32 }      

接下來實作資訊分組的輸入,這個函數接受一個位元組數組作為下一個消息分組以便進行處理。

1 SHAStatusCode SHA512Input(SHA512Context *context,const uint8_t *message_array,unsigned int length)
 2 {
 3   if (!context) return shaNull;
 4   if (!length) return shaSuccess;
 5   if (!message_array) return shaNull;
 6   if (context->Computed) return context->Corrupted = shaStateError;
 7   if (context->Corrupted) return context->Corrupted;
 8 
 9   while (length--)
10   {
11     context->Message_Block[context->Message_Block_Index++] =*message_array;
12 
13     if ((SHA384_512AddLength(context, 8) == shaSuccess) &&
14     (context->Message_Block_Index == SHA512_Message_Block_Size))
15       SHA384_512ProcessMessageBlock(context);
16     message_array++;
17   }
18   return context->Corrupted;
19 }      

當然還需要一個消息處理及最終摘要輸出的函數,這個函數将傳回一個384位或者512位的資訊摘要到調用者給定的Message_Digest數組。傳回的資訊摘要,第一個元素索引為0,最後一個元素索引為47(SHA-384)或者63(SHA-512)。

1 static SHAStatusCode SHA384_512ResultN(SHA512Context *context,uint8_t Message_Digest[ ], int HashSize)
 2 {
 3   int i;
 4 
 5 #ifdef USE_32BIT_ONLY
 6   int i2;
 7 #endif /* USE_32BIT_ONLY */
 8 
 9   if (!context) return shaNull;
10   if (!Message_Digest) return shaNull;
11   if (context->Corrupted) return context->Corrupted;
12   if (!context->Computed)
13 
14   SHA384_512Finalize(context, 0x80);
15 
16 #ifdef USE_32BIT_ONLY
17   for (i = i2 = 0; i < HashSize; ) {
18     Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>24);
19     Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>16);
20     Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>8);
21     Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2++]);
22     Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>24);
23     Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>16);
24     Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2]>>8);
25     Message_Digest[i++]=(uint8_t)(context->Intermediate_Hash[i2++]);
26   }
27 
28 #else /* !USE_32BIT_ONLY */
29   for (i = 0; i < HashSize; ++i)
30     Message_Digest[i] = (uint8_t)(context->Intermediate_Hash[i>>3] >> 8 * ( 7 - ( i % 8 ) ));
31 #endif /* USE_32BIT_ONLY */
32 
33   return shaSuccess;
34 }      

至此,我們就完成了SHA-512(SHA-384)的編碼,在後續我們将對這一編碼進行驗證。

6、結論

上一節我們實作了SHA-512(SHA-384)的編碼,接下來我們來對這一實作進行驗證。我們輸入明文“abcd”并輸出結果:

資訊摘要算法之四:SHA512算法分析與實作

同樣,我們也采用其他工具生成同一資訊輸入(“abcd”)的SHA-512資訊摘要如下:

資訊摘要算法之四:SHA512算法分析與實作

我們對比上述兩項的輸出結果,很顯然是完全一緻的,這說明我們的SHA-512的編碼實作是正确的。

歡迎關注:

如果您希望更友善且及時的閱讀相關文章,關注我的微信公衆号【木南創智】