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 參考文獻
1: FIPS 180-1 Secure Hash Standard: http://www.itl.nist.gov/fipspubs/fip180-1.htm
2: Secure Hash Standard: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
實現代碼如下:
using System; using System.Collections; using System.IO; using System.Text; namespace VerifySHA1 { public class MySHA1 { // state variables private static UInt32 Message_Digest1 = 0x67452301; private static UInt32 Message_Digest2 = 0xEFCDAB89; private static UInt32 Message_Digest3 = 0x98BADCFE; private static UInt32 Message_Digest4 = 0x10325476; private static UInt32 Message_Digest5 = 0xC3D2E1F0; private static UInt32 SHA1CircularShift(int bits,UInt32 word) { return ((word << bits) & 0xFFFFFFFF) | (word) >> (32-(bits)); } private static void SHA1_Init() { Message_Digest1 = 0x67452301; Message_Digest2 = 0xEFCDAB89; Message_Digest3 = 0x98BADCFE; Message_Digest4 = 0x10325476; Message_Digest5 = 0xC3D2E1F0; } private static 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; } else if (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; } private static 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; } return new UInt32[]{Message_Digest1,Message_Digest2,Message_Digest3,Message_Digest4,Message_Digest5}; } public static string 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(); } public static string MySHA1String(string message) { char[] c = message.ToCharArray(); byte[] b = new byte[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); } public static string MySHA1File(string fileName) { FileStream fs=File.Open(fileName,FileMode.Open,FileAccess.Read); byte[] array=new byte[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 Test public static string Test(string message) { return "\r\nSHA1 (\""+message+"\") = " + MySHA1String(message); } public static string 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; } public static void Main() { Console.WriteLine(MySHA1.TestSuite()); Console.ReadLine(); } #endregion } }
OC 代碼:
- (NSString *)stringWithSHA_1 { // 1. 轉化為 c 類型 const char *cString = [self UTF8String]; // 2. 求長度 CC_LONG len = (CC_LONG)strlen(cString); unsigned char bytes[CC_SHA1_DIGEST_LENGTH]; // 3. SHA_1轉換 CC_SHA1(cString, len, bytes); NSMutableString *finalSHA1 = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH]; for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) { [finalSHA1 appendFormat:@"%02x", bytes[i]]; } return finalSHA1; }