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
首先补一个“
补位第二步:01100001 01100010 01100011 10…..0
然后补423个“
我们可以把最后补位完成后的数据用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 = 0x
Kt = 0x6ED9EBA1 (20 <= t <= 39)
Kt = 0x
Kt = 0xCA
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 = 0xC3D2E
现在开始处理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.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
}
}
/******************************************************************************************
*【Author】:flyingbread
*【Date】:2007年1月2日
*【Notice】:
*1、本文为原创技术文章,首发博客园个人站点(http://flyingbread.cnblogs.com/),转载和引用请注明作者及出处。
*2、本文必须全文转载和引用,任何组织和个人未授权不能修改任何内容,并且未授权不可用于商业。
*3、本声明为文章一部分,转载和引用必须包括在原文中。
******************************************************************************************/