現代密碼學實驗6 散列算法SHA-1的實現

讚賞碼 & 聯繫方式 & 個人閒話

【實驗名稱】散列算法SHA-1的實現

【實驗目的】

1、瞭解散列算法SHA-1的實現過程。

2、實現並掌握SHA-1散列算法。

【實驗原理】

安全散列算法是一個密碼散列函數家族,是FIPS所認證的安全散列算法。能計算出一個數字消息所對應到的,長度固定的字符串(又稱消息摘要)的算法。且若輸入的消息不同,它們對應到不同字符串的機率很高。

SHA-1(英語:Secure Hash Algorithm 1,中文名:安全散列算法1)是一種密碼散列函數。SHA-1可以生成一個被稱爲消息摘要的160位(20字節)散列值,散列值通常的呈現形式爲40個十六進制數。主要包括:字符轉換、補位及補長度、算法預處理、處理每個散列塊Mi等幾個步驟。

【實驗內容】

實驗內容: 使用散列算法SHA-1計算“abc”的散列值

代碼:(代碼解釋見代碼中註釋)

#include<iostream>
#include<string>
using namespace std;
#define LL long long 

//字符轉換,string轉int
void trans(string In, int* input)
{
	int i, j;
	int len = In.length();
	for (i = 0, j = 0; i < len; i++)
	{
		if (i != 0 && i % 4 == 0)
		{
			j++;
		}
		//int aaa = int(In[i]);
		input[j] = (input[j] << 8) + int(In[i]);
	}
	if (In.length() % 4 == 1)
	{
		input[j] = input[j] << 24;
	}
	else if(In.length() % 4 == 2)
	{
		input[j] = input[j] << 16;
	}
	else if (In.length() % 4 == 3)
	{
		input[j] = input[j] << 8;
	}
}

//補位及補長度
void supplement(LL len, int *input)
{
	if (len % 4 == 0)
	{
		input[len / 4] += 0x80000000;
	}
	else if (len % 4 == 1)
	{
		input[len / 4] += 0x00800000;
	}
	else if (len % 4 == 2)
	{
		input[len / 4] += 0x00008000;
	}
	else if (len % 4 == 3)
	{
		input[len / 4] += 0x00000080;
	}


	input[14] = ((8 * len) & 0xffffffff00000000) >> 8;
	input[15] = (8 * len) & 0x00000000ffffffff;
}

//循環左移n位
int ROL(int a, int n)
{
	//注意強制類型轉換爲無符號整數
	a = ((unsigned)a >> (32 - n)) + ((unsigned)a << n);
	return a;
}

//函數ft(B,C,D) 
int Ft(int i, int *HH)
{
	int result;
	if (i >= 0 && i <= 19)
	{
		result = (HH[1] & HH[2]) | ((~HH[1]) & HH[3]);
	}
	else if (i >= 20 && i <= 39)
	{
		result = HH[1] ^ HH[2] ^ HH[3];
	}
	else if (i >= 40 && i <= 59)
	{
		result = (HH[1] & HH[2]) | (HH[1] & HH[3]) | (HH[2] & HH[3]);
	}
	else if (i >= 60 && i <= 79)
	{
		result = HH[1] ^ HH[2] ^ HH[3];
	}
	return result;
}

//常量Kt
int KT(int i)
{
	int Kt[4], result;
	Kt[0] = 0x5A827999;
	Kt[1] = 0x6ED9EBA1;
	Kt[2] = 0x8F1BBCDC;
	Kt[3] = 0xCA62C1D6;
	if (i >= 0 && i <= 19)
	{
		result = Kt[0];
	}
	else if (i >= 20 && i <= 39)
	{
		result = Kt[1];
	}
	else if (i >= 40 && i <= 59)
	{
		result = Kt[2];
	}
	else if (i >= 60 && i <= 79)
	{
		result = Kt[3];
	}
	return result;
}

//生成消息Wt
void WT(int i,int *Wt,int *input)
{
	if (i >= 0 && i <= 15)
	{
		Wt[i] = input[i];
	}
	else
	{
		Wt[i] = ROL(Wt[i - 3] ^ Wt[i - 8] ^ Wt[i - 14] ^ Wt[i - 16], 1);
	}
	return;
}

//對散列塊進行處理
void deal(int *HH, int *input,int *H)
{
	int Wt[80];
	for (int i = 0; i < 80; i++)
	{
		int ft, kt, temp;
		
		//計算函數Ft(B,C,D) 
		ft = Ft(i, HH);
		//對應Kt
		kt = KT(i);
		//計算wt
		WT(i, Wt, input);
		int aaaa = ROL(HH[0], 5);
		int bbbb = Wt[i];
		//循環左移5位
		temp = ROL(HH[0], 5) + ft + HH[4] + kt + Wt[i];
		
		//跟新ABCDE五個寄存器
		HH[4] = HH[3];
		HH[3] = HH[2];
		HH[2] = ROL(HH[1], 30);
		HH[1] = HH[0];
		HH[0] = temp;

		//printf("%x %x %x %x %x\n", HH[0], HH[1], HH[2], HH[3], HH[4]);
	}
	//分別與H0,H1,H2,H3,H4相加
	for (int i = 0; i < 5; i++)
	{
		H[i] += HH[i];
	}
}

int main()
{
	//輸入
	cout << "\n請輸入待散列的數據:";
	string In;
	cin >> In;

	cout << "\n\n*****************************" << endl;
	cout << "***  SHA-1散列算法計算中  ***" << endl;
	cout << "*****************************\n\n" << endl;

	int input[16];
	memset(input, 0, sizeof(input));
	//字符轉換
	trans(In, input);
	//補位以及補長度
	supplement(In.length(), input);

	//HH爲ABCDE寄存器
	int HH[5] = { 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0 };
	//H爲初始數據塊
	int H[5] = { 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0 };
	//對散列塊進行處理
	deal(HH, input, H);

	//輸出哈希結果
	cout << "散列值爲:";
	for (int i = 0; i < 5; i++)
	{
		//16進制輸出
		cout << hex << H[i];
	}
	cout << endl << endl << endl;
	return 0;
}

運行演示:

 

在網上找了一個在線進行SHA-1散列的網站驗證結果,發現結果正確:

【小結或討論】

這一次的實驗應該說難度還是有的,這讓我回想起了之前的AES算法,如果單純從代碼實現的角度來說,兩者是有一定程度相似的,都比較考驗對每個字節甚至每一bit的操作能力,非常考驗細節,一處細小的錯誤就會導致滿盤皆輸,因爲錯誤會在算法的循環中不斷累加。不過好在之前實驗的鍛鍊,讓我更快地理解了SHA-1散列算法原理,並且編程實現了它。

雖說有了經驗,但實驗過程當然也不是一帆風順的,debug的過程是相當繁雜的,只能依靠手動計算的結果和代碼計算的結果相互對比驗證,尋找錯誤到底出在哪裏。

我印象最深的還是我在寫位移函數時留下的bug。我一開始是這樣寫的:a = (a >> (32 - n)) + (a << n); 其實這樣咋一看是沒有毛病的,後面的(32-n)位加上原先前面的n位不就是循環左移嘛。但其實在右移時,如果最高位是“1”,那麼默認是補“1”而不是補“0”,也就是說它默認是當作有符號數處理的!這個bug太過隱蔽,我是單步調試一點一點才把它找到的。所以只要加一個強制類型轉換,聲明爲無符號整數,就像這樣:a = ((unsigned)a >> (32 - n)) + ((unsigned)a << n);就成功解決了這個bug。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章