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

資訊摘要演算法之二:SHA1演算法分析及實現

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

1、概述

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

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

類別

SHA-1

SHA-224

SHA-256

SHA-384

SHA-512

訊息摘要長度

160

224

256

384

512

訊息長度

小於位

小於位

小於位

小於位

小於位

分組長度

512

512

512

1024

1024

計算字長度

32

32

32

64

64

計算步驟數

80

64

64

80

80

SHA-1在許多安全協定中廣為使用,包括TLSSSLPGPSSH

S/MIMEIPsec,曾被視為是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位的長度進行分組,可以得到一定數量的明文分組,我們用Y0Y1……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個連結變數。我們記為ABCDE。其初始賦值分別為:A = 0x67452301B = 0xEFCDAB89、C = 0x98BADCFE、D = 0x10325476、E = 0xC3D2E1F0

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

5)計算資訊摘要

經過前面的準備,接下來就是計算資訊摘要了。SHA14輪運算,每一輪包括20個步驟,一共80步,最終產生160位的資訊摘要,這160位的摘要存放在532位的連結變數中。

SHA14論運算中,雖然進行的就具體操作函式不同,但邏輯過程卻是一致的。首先,定義5個變數,假設為H0H1H2H3H4,對其分別進行如下操作:

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

B)、將A的值賦予H1

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

D)、將C的值賦予H3

E)、將D的值賦予H4

F)、最後將H0H1H2H3H4的值分別賦予ABCD

這一過程表示如下:

 

而在480步的計算中使用到的函式和固定常熟如下表所示:

計算輪次

計算的步數

計算函式

計算常數

第一輪

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

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

3、軟體實現

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

/*  SHA1Reset函式用於初始化SHA1的內容值                          */

/*  引數:contextSHA的內容值,儲存計算結果既初始值,輸入輸出      */

/*  返回值:SHA錯誤程式碼                                            */

ErrorCode SHA1Reset(SHA1Context *context)

{

  if (!context)

  {

    return shaNull;

    }

  context->Length_Low             = 0;

  context->Length_High            = 0;

  context->Message_Block_Index    = 0;

  context->Intermediate_Hash[0]   = 0x67452301;

  context->Intermediate_Hash[1]   = 0xEFCDAB89;

  context->Intermediate_Hash[2]   = 0x98BADCFE;

  context->Intermediate_Hash[3]   = 0x10325476;

  context->Intermediate_Hash[4]   = 0xC3D2E1F0;

  context->Computed   = 0;

  context->Corrupted  = 0;

  return shaSuccess;

}

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

/*  SHA1Input函式,將分組的資訊讀入並進行摘要計算               */

/*  引數:                                                       */

/*      contextSHA的內容值,儲存計算結果既初始值,輸入輸出     */

/*      message_array,待處理的資訊分組的位元組陣列,輸入引數       */

/*      lengthmessage_array陣列中資訊的長度                      */

/*  返回值:SHA錯誤程式碼                                         */

ErrorCode SHA1Input(SHA1Context *context,const uint8_t *message_array,unsigned length)

{

  if (!length)

  {

    return shaSuccess;

  }

  if (!context || !message_array)

  {

    return shaNull;

  }

  if (context->Computed)

  {

    context->Corrupted = shaStateError;

    return shaStateError;

  }

  if (context->Corrupted)

  {

    return (ErrorCode)context->Corrupted;

  }

  while(length-- && !context->Corrupted)

  {

    context->Message_Block[context->Message_Block_Index++] =

                    (*message_array & 0xFF);

    context->Length_Low += 8;

    if (context->Length_Low == 0)

    {

      context->Length_High++;

      if (context->Length_High == 0)

      {

        /* 訊息長度超過限值 */

        context->Corrupted = 1;

      }

    }

    if (context->Message_Block_Index == 64)

    {

        SHA1ProcessMessageBlock(context);

    }

    message_array++;

  }

  return shaSuccess;

}

然後實現結果輸出函式,該函式對資訊的最後部分進行處理並返回160位的資訊摘要到Message_Digest陣列,該陣列作為引數有呼叫者輸入。第一個元素存第一個位元組,依次20個位元組。

/*  SHA1Result函式,對資訊的最後部分進行處理並輸出最終計算結果 */

/*  引數:                                                     */

/*      contextSHA的內容值,儲存計算結果既初始值,輸入輸出   */

/*      Message_Digest,資訊摘要的返回值,輸出引數              */

/*  返回值:SHA錯誤程式碼                                       */

ErrorCode SHA1Result( SHA1Context *context,uint8_t Message_Digest[SHA1HashSize])

{

  int i;

  if (!context || !Message_Digest)

  {

    return shaNull;

  }

  if (context->Corrupted)

  {

    return (ErrorCode)context->Corrupted;

  }

  if (!context->Computed)

  {

    SHA1PadMessage(context);

    for(i=0; i<64; ++i)

    {

      /* 處理完畢,清除訊息分組 */

      context->Message_Block[i] = 0;

    }

    context->Length_Low = 0;    /* 清除長度資料 */

    context->Length_High = 0;

    context->Computed = 1;

  }

  for(i = 0; i < SHA1HashSize; ++i)

  {

    Message_Digest[i] = context->Intermediate_Hash[i>>2]>>8*(3-(i&0x03));

  }

  return shaSuccess;

}

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

/*  SHA1ProcessMessageBlock函式,處理訊息分組                    */

/*  描述:                                                       */

/*  引數:                                                       */

/*      contextSHA的內容值,儲存計算結果既初始值,輸入輸出     */

/*  返回值:無                                                   */

static void SHA1ProcessMessageBlock(SHA1Context *context)

{

  /* SHA-1計算中用到的常數定義 */

  const uint32_t K[]={0x5A827999,0x6ED9EBA1,0x8F1BBCDC,0xCA62C1D6};

  int t;                         /* 迴圈變數 */

  uint32_t temp;                /* 臨時變數,存計算值 */

  uint32_t W[80];               /* 子明文分組陣列 */

  uint32_t A, B, C, D, E;       /* 初始值快取變數 */

  /* 初始化子明文分組W的前16個字 */

  for(t = 0; t < 16; t++)

  {

    W[t] = context->Message_Block[t * 4] << 24;

    W[t] |= context->Message_Block[t * 4 + 1] << 16;

    W[t] |= context->Message_Block[t * 4 + 2] << 8;

    W[t] |= context->Message_Block[t * 4 + 3];

  }

  for(t = 16; t < 80; t++)

  {

    W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);

  }

  A = context->Intermediate_Hash[0];

  B = context->Intermediate_Hash[1];

  C = context->Intermediate_Hash[2];

  D = context->Intermediate_Hash[3];

  E = context->Intermediate_Hash[4];

  /*120步計算*/

  for(t = 0; t < 20; t++)

  {

    temp =  SHA1CircularShift(5,A)+((B & C) | ((~B) & D)) + E + W[t] + K[0];

    E = D;

    D = C;

    C = SHA1CircularShift(30,B);

    B = A;

    A = temp;

    }

  /*220步計算*/

  for(t = 20; t < 40; t++)

  {

    temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];

    E = D;

    D = C;

    C = SHA1CircularShift(30,B);

    B = A;

    A = temp;

  }

  /*320步計算*/

  for(t = 40; t < 60; t++)

  {

    temp = SHA1CircularShift(5,A)+((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];

    E = D;

    D = C;

    C = SHA1CircularShift(30,B);

    B = A;

    A = temp;

  }

  /*420步計算*/

  for(t = 60; t < 80; t++)

  {

    temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];

    E = D;

    D = C;

    C = SHA1CircularShift(30,B);

    B = A;

    A = temp;

  }

  context->Intermediate_Hash[0] += A;

  context->Intermediate_Hash[1] += B;

  context->Intermediate_Hash[2] += C;

  context->Intermediate_Hash[3] += D;

  context->Intermediate_Hash[4] += E;

  context->Message_Block_Index = 0;

}

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

/*  SHA1PadMessage函式,補全訊息,並新增長度,計算最終結果      */

/*  引數:                                                       */

/*      contextSHA的內容值,儲存計算結果既初始值,輸入輸出     */

/*  返回值:無                                                   */

static void SHA1PadMessage(SHA1Context *context)

{

  /* 檢查當前的訊息分組,如果小於等於55個位元組,則直接新增補位和長度資訊。否則,如果大於55個位元組,我們填充塊到512位,並處理它,然後繼續填充到第二個塊中直道448位,最後填寫長度資訊。*/

  if (context->Message_Block_Index > 55)

  {

    context->Message_Block[context->Message_Block_Index++] = 0x80;

    while(context->Message_Block_Index < 64)

    {

      context->Message_Block[context->Message_Block_Index++] = 0;

    }

    SHA1ProcessMessageBlock(context);

    while(context->Message_Block_Index < 56)

    {

      context->Message_Block[context->Message_Block_Index++] = 0;

    }

  }

  else

  {

    context->Message_Block[context->Message_Block_Index++] = 0x80;

    while(context->Message_Block_Index < 56)

    {

      context->Message_Block[context->Message_Block_Index++] = 0;

    }

  }

  /* 將明文長度填入到最後8個位元組中 */

  context->Message_Block[56] = context->Length_High >> 24;

  context->Message_Block[57] = context->Length_High >> 16;

  context->Message_Block[58] = context->Length_High >> 8;

  context->Message_Block[59] = context->Length_High;

  context->Message_Block[60] = context->Length_Low >> 24;

  context->Message_Block[61] = context->Length_Low >> 16;

  context->Message_Block[62] = context->Length_Low >> 8;

  context->Message_Block[63] = context->Length_Low;

  SHA1ProcessMessageBlock(context);

}

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

/*  定義SHA-1內容儲存結構體  */

typedef struct SHA1Context

{

    uint32_t Intermediate_Hash[SHA1HashSize/4];         /* 資訊摘要 */

    uint32_t Length_Low;                                /* 按位計算的資訊長度低字 */

    uint32_t Length_High;                               /* 按位計算的資訊長度高字 */

    int_least16_t Message_Block_Index;                  /* 資訊分組陣列的索引 */

    uint8_t Message_Block[64];                          /* 512位資訊分組 */

    int Computed;                                       /* 摘要計算標識 */

    int Corrupted;                                      /* 資訊摘要損壞標識 */

} SHA1Context;

typedef enum

{

    shaSuccess = 0,     /* 處理成功 */

    shaNull,            /* 指標引數為NUll */

    shaInputTooLong,    /* 輸入訊息長度超範圍 */

    shaStateError       /* 在處理完畢後,未經初始化直接呼叫輸入處理 */

}ErrorCode;

4、總結

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

 

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

 


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

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

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

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

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

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

歡迎關注: