SHA1演算法實現及詳解
1 SHA1演算法簡介
安全雜湊演算法(Secure Hash Algorithm)主要適用於數字簽名標準(Digital Signature Standard DSS)裡面定義的數字簽名演算法(Digital Signature Algorithm DSA)。對於長度小於2^64位的訊息,SHA1會產生一個160位的訊息摘要。當接收到訊息的時候,這個訊息摘要可以用來驗證資料的完整性。在傳輸的過程中,資料很可能會發生變化,那麼這時候就會產生不同的訊息摘要。
SHA1有如下特性:不可以從訊息摘要中復原資訊;兩個不同的訊息不會產生同樣的訊息摘要。
2 術語和概念
2.1位(Bit),位元組(Byte)和字(Word)
SHA1始終把訊息當成一個位(bit)字串來處理。本文中,一個“字”(Word)是32位,而一個“位元組”(Byte)是8位。比如,字串“abc”可以被轉換成一個位字串:01100001 01100010 01100011。它也可以被表示成16進位制字串: 0x616263.
2.2 運算子和符號
下面的邏輯運算子都被運用於“字”(Word)
X^Y = X, Y邏輯與
X \/ Y = X, Y邏輯或
X XOR Y= X, Y邏輯異或
~X = X邏輯取反
X+Y定義如下:
字 X 和 Y 代表兩個整數 x 和y, 其中 0 <= x < 2^32 且 0 <= y < 2^32. 令整數z = (x + y) mod 2^32. 這時候 0 <= z < 2^32. 將z轉換成字Z, 那麼就是 Z = X + Y.
迴圈左移位操作符Sn(X)。X是一個字,n是一個整數,0<=n<=32。Sn(X) = (X<<n)OR(X>>32-n)
X<<n定義如下:拋棄最左邊的n位數字,將各個位依次向左移動n位,然後用0填補右邊的n位(最後結果還是32位)。X>>n是拋棄右邊的n位,將各個位依次向右移動n位,然後在左邊的n位填0。因此可以叫Sn(X)位迴圈移位運算
3 SHA1演算法描述
在SHA1演算法中,我們必須把原始訊息(字串,檔案等)轉換成位字串。SHA1演算法只接受位作為輸入。假設我們對字串“abc”產生訊息摘要。首先,我們將它轉換成位字串如下:
01100001 01100010 01100011
―――――――――――――
‘a’=97 ‘b’=98 ‘c’=99
這個位字串的長度為24。下面我們需要5個步驟來計算MD5。
3.1 補位
訊息必須進行補位,以使其長度在對512取模以後的餘數是448。也就是說,(補位後的訊息長度)%512 = 448。即使長度已經滿足對512取模後餘數是448,補位也必須要進行。
補位是這樣進行的:先補一個1,然後再補0,直到長度滿足對512取模後餘數是448。總而言之,補位是至少補一位,最多補512位。還是以前面的“abc”為例顯示補位的過程。
原始資訊: 01100001 01100010 01100011
補位第一步:01100001 01100010 01100011 1
首先補一個“1”
補位第二步:01100001 01100010 01100011 10…..0
然後補423個“0”
我們可以把最後補位完成後的資料用16進位制寫成下面的樣子
61626380 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000
現在,資料的長度是448了,我們可以進行下一步操作。
3.2 補長度
所謂的補長度是將原始資料的長度補到已經進行了補位操作的訊息後面。通常用一個64位的資料來表示原始訊息的長度。如果訊息長度不大於2^64,那麼第一個字就是0。在進行了補長度的操作以後,整個訊息就變成下面這樣了(16進位制格式)
61626380 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000018
如果原始的訊息長度超過了512,我們需要將它補成512的倍數。然後我們把整個訊息分成一個一個512位的資料塊,分別處理每一個數據塊,從而得到訊息摘要。
3.3 使用的常量
一系列的常量字K(0), K(1), ... , K(79),如果以16進位制給出。它們如下:
Kt = 0x5A827999 (0 <= t <= 19)
Kt = 0x6ED9EBA1 (20 <= t <= 39)
Kt = 0x8F1BBCDC (40 <= t <= 59)
Kt = 0xCA62C1D6 (60 <= t <= 79).
3.4 需要使用的函式
在SHA1中我們需要一系列的函式。每個函式ft (0 <= t <= 79)都操作32位字B,C,D並且產生32位字作為輸出。ft(B,C,D)可以如下定義
ft(B,C,D) = (B AND C) OR ((NOT B) AND D) ( 0 <= t <= 19)
ft(B,C,D) = B XOR C XOR D (20 <= t <= 39)
ft(B,C,D) = (B AND C) OR (B AND D) OR (C AND D) (40 <= t <= 59)
ft(B,C,D) = B XOR C XOR D (60 <= t <= 79).
3.5 計算訊息摘要
必須使用進行了補位和補長度後的訊息來計算訊息摘要。計算需要兩個緩衝區,每個都由5個32位的字組成,還需要一個80個32位字的緩衝區。第一個5個字的緩衝區被標識為A,B,C,D,E。第一個5個字的緩衝區被標識為H0,H1, H2, H3, H4
。80個字的緩衝區被標識為W0, W1,..., W79
另外還需要一個一個字的TEMP緩衝區。
為了產生訊息摘要,在第4部分中定義的16個字的資料塊M1, M2,..., Mn
會依次進行處理,處理每個資料塊Mi 包含80個步驟。
在處理每個資料塊之前,緩衝區{Hi} 被初始化為下面的值(16進位制)
H0 = 0x67452301
H1 = 0xEFCDAB89
H2 = 0x98BADCFE
H3 = 0x10325476
H4 = 0xC3D2E1F0.
現在開始處理M1, M2, ... , Mn。為了處理 Mi,需要進行下面的步驟
(1). 將 Mi 分成 16 個字 W0, W1, ... , W15, W0 是最左邊的字
(2). 對於 t = 16 到 79 令 Wt = S1(Wt-3 XOR Wt-8 XOR Wt- 14 XOR Wt-16).
(3). 令 A = H0, B = H1, C = H2, D = H3, E = H4.
(4) 對於 t = 0 到 79,執行下面的迴圈
TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt;
E = D; D = C; C = S30(B); B = A; A = TEMP;
(5). 令 H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
在處理完所有的 Mn, 後,訊息摘要是一個160位的字串,以下面的順序標識
H0 H1 H2 H3 H4.
對於SHA256,SHA384,SHA512。你也可以用相似的辦法來計算訊息摘要。對訊息進行補位的演算法完全是一樣的。
4 參考文獻
實現程式碼如下:
using System.Collections;
using System.IO;
using System.Text;
namespace VerifySHA1
{
publicclass MySHA1
{
// state variables
privatestatic UInt32 Message_Digest1 =0x67452301;
privatestatic UInt32 Message_Digest2 =0xEFCDAB89;
privatestatic UInt32 Message_Digest3 =0x98BADCFE;
privatestatic UInt32 Message_Digest4 =0x10325476;
privatestatic UInt32 Message_Digest5 =0xC3D2E1F0;
privatestatic UInt32 SHA1CircularShift(int bits,UInt32 word)
{
return ((word << bits) &0xFFFFFFFF) | (word) >> (32-(bits));
}
privatestaticvoid SHA1_Init()
{
Message_Digest1 =0x67452301;
Message_Digest2 =0xEFCDAB89;
Message_Digest3 =0x98BADCFE;
Message_Digest4 =0x10325476;
Message_Digest5 =0xC3D2E1F0;
}
privatestatic UInt32[] SHA1_Append(byte[] input)
{
int zeros=0;
int ones =1;
int size=0;
int n = input.Length;
int m = n%64;
if( m <56 )
{
zeros =55-m;
size=n-m+64;
}
elseif (m==56)
{
zeros =63;
ones =1;
size=n+8+64;
}
else
{
zeros =63-m+56;
size=n+64-m+64;
}
ArrayList bs =new ArrayList(input);
if(ones==1)
{
bs.Add( (byte)0x80 ); // 0x80 = 10000000
}
for(int i=0;i<zeros;i++)
{
bs.Add( (byte)0 );
}
UInt64 N = (UInt64) n *8;
byte h8=(byte)(N&0xFF);
byte h7=(byte)((N>>8)&0xFF);
byte h6=(byte)((N>>16)&0xFF);
byte h5=(byte)((N>>24)&0xFF);
byte h4=(byte)((N>>32)&0xFF);
byte h3=(byte)((N>>40)&0xFF);
byte h2=(byte)((N>>48)&0xFF);
byte h1=(byte)(N>>56);
bs.Add(h1);
bs.Add(h2);
bs.Add(h3);
bs.Add(h4);
bs.Add(h5);
bs.Add(h6);
bs.Add(h7);
bs.Add(h8);
byte[] ts=(byte[])bs.ToArray(typeof(byte));
/* Decodes input (byte[]) into output (UInt32[]). Assumes len is
* a multiple of 4.
*/
UInt32[] output =new UInt32[size/4];
for(Int64 i=0,j=0;i<size;j++,i+=4)
{
UInt32 temp =0;
temp=temp|(((UInt32)ts[i])<<24);
temp=temp|(((UInt32)ts[i+1])<<16);
temp=temp|(((UInt32)ts[i+2])<<8);
temp=temp|(((UInt32)ts[i+3]));
output[j] = temp;
}
return output;
}
privatestatic UInt32[] SHA1_Transform(UInt32[] x)
{
SHA1_Init();
UInt32[] K = {
0x5A827999,
0x6ED9EBA1,
0x8F1BBCDC,
0xCA62C1D6
};
int t;
UInt32 temp;
UInt32[] W =new UInt32[80];
UInt32 A, B, C, D, E;
for(int k=0; k<x.Length; k+=16)
{
for(t =0; t <16; t++)
{
W[t] = x[t+k];
}
for(t =16; t <80; t++)
{
W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
}
A = Message_Digest1;
B = Message_Digest2;
C = Message_Digest3;
D = Message_Digest4;
E = Message_Digest5;
for(t =0; t <20; t++)
{
temp = SHA1CircularShift(5,A) +
((B & C) | ((~B) & D)) + E + W[t] + K[0];
temp &=0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t =20; t <40; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
temp &=0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t =40; t <60; t++)
{
temp = SHA1CircularShift(5,A) +
((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
temp &=0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t =60; t <80; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
temp &=0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
Message_Digest1 = (Message_Digest1 + A) &0xFFFFFFFF;
Message_Digest2 = (Message_Digest2 + B) &0xFFFFFFFF;
Message_Digest3 = (Message_Digest3 + C) &0xFFFFFFFF;
Message_Digest4 = (Message_Digest4 + D) &0xFFFFFFFF;
Message_Digest5 = (Message_Digest5 + E) &0xFFFFFFFF;
}
returnnew UInt32[]{Message_Digest1,Message_Digest2,Message_Digest3,Message_Digest4,Message_Digest5};
}
publicstaticstring SHA1Array(UInt32[] input)
{
StringBuilder sb =new StringBuilder();
for(int i=0; i<input.Length; i++)
{
sb.Append( String.Format("{0:X8}", input[i]).ToUpper() );
}
return sb.ToString();
}
publicstaticstring MySHA1String(string message)
{
char[] c = message.ToCharArray();
byte[] b =newbyte[c.Length];
for(int i=0;i<c.Length;i++)
{
b[i]=(byte)c[i];
}
UInt32[] output = SHA1_Append( b );
UInt32[] str = SHA1_Transform( output );
return SHA1Array(str);
}
publicstaticstring MySHA1File(string fileName)
{
FileStream fs=File.Open(fileName,FileMode.Open,FileAccess.Read);
byte[] array=newbyte[fs.Length];
fs.Read(array,0,(int)fs.Length);
fs.Close();
UInt32[] output = SHA1_Append( array );
UInt32[] str = SHA1_Transform( output );
return SHA1Array(str);
}
#region Unit Testpublicstaticstring Test(string message)
{
return"\r\nSHA1 (\""+message+"\") = "+ MySHA1String(message);
}
publicstaticstring TestSuite()
{
string s ="";
s+=Test("");
s+=Test("a");
s+=Test("abc");
s+=Test("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
s+=Test("abcdefghijklmnopqrstuvwxyz");
s+=Test("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
s+=Test("12345678901234567890123456789012345678901234567890123456789012345678901234567890");
// StringBuilder sb = new StringBuilder();
// for(int i=0; i<1000000; i++)
// sb.Append("a");
// s+=Test(sb.ToString());
return s;
}
publicstaticvoid Main()
{
Console.WriteLine(MySHA1.TestSuite());
Console.ReadLine();
}
#endregion
}
}
/******************************************************************************************
*【Author】:flyingbread
*【Date】:2007年1月2日
*【Notice】:
*1、本文為原創技術文章,首發部落格園個人站點(http://flyingbread.cnblogs.com/),轉載和引用請註明作者及出處。
*2、本文必須全文轉載和引用,任何組織和個人未授權不能修改任何內容,並且未授權不可用於商業。
*3、本宣告為文章一部分,轉載和引用必須包括在原文中。
******************************************************************************************/