SHA-1算法解释与C/C++实现

SHA-1

与MD5算法一样,也属于是一种哈希算法,并非是密码算法,也是因为其不可逆性,导致其在密码界常有应用,他与MD5的区别:

  • 输出定长为20字节
  • 安全强度更强
  • 算法计算过程略有不同

算法实现过程

  1. 与其他哈希算法一样,首先肯定是对原文数据进行填充,输入不定长度的原文,要使其长度满足: “长度 mod 512bit =448bit”
  2. 不管其长度是否满足,都要在其后至少添加一个十进制数128即0x80,其二进制形式为1000 0000,如果长度不满足就在添加完0x80后继续填充0,一致填充到满足"长度 mod 512bit =448bit "为止
  3. 在添加完0x80后添加原文数据长度,记住原文数据长度要占八字节,最后记得初始化五组32bit的幻数,分别为:A=0x67452301,B=0xefcdab89,C=0x98badcfe,D=0x10325476,E=0xc3d2e1f0,可以看出比MD5算法多出了一个E
  4. 在进行完上述步骤后,不出错的话,此时填充后的数据长度刚好是512bit即64byte的倍数,然后开始每512bit一算
  5. 现在我们每组能获取一个64字节的数据,但是要注意SHA-1算法与MD5算法不同就在于,MD5算法是再将这64字节分为4组每组4个字节共16个字节共四轮运算,4*16最后将这64个字节处理完,而SHA-1则也是分为四组每组4个字节,一共四轮计算,但不同的是因为其输出长度固定为20字节,所以我们要将填充后的数据长度再进行处理,处理过程:
for (int i = 0; i < 16; i++)
	{
		int j = 4 * i;
		ulong_data[i] = ((unsigned int)char_data[j]) << 24 | 
						((unsigned int)char_data[1 + j]) << 16 | 
						((unsigned int)char_data[2 + j]) << 8 |
						((unsigned int)char_data[3 + j]) << 0;
	}
	for (int i = 16; i < 80; i++)
	{
		ulong_data[i] = ulong_data[i - 16] ^ ulong_data[i - 14] ^ 
						ulong_data[i - 8] ^ ulong_data[i - 3];
		ulong_data[i] = (ulong_data[i] << 1) | (ulong_data[i] >> 31);
	}

首先是将原本的64个字节的字符数据统一处理成一组四个字节的无符号整型数据,然后再在其后填充,一填充到满足80个无符号整形数据位置
6. 现在处理就完成了,我们开始用80/20刚好得4组每组四个字节
7. 然后将ABCDE五组幻数进行移动,D->E,C->E,然后对C进行计算处理,A->B然后对A进行计算处理,一次循环计算
8. 最终将每轮计算的ABCDE相加得出最后结果

代码实现

首先初始化一下我们要使用到的一些常量比如五组幻数:

	unsigned int k = 0, f = 0;
	unsigned int temp_ul_text[80];
	unsigned int temp_A = 0;
	unsigned int h[] = { 0x67452301,0xefcdab89,0x98badcfe,0x10325476,0xc3d2e1f0 };

然后开始填充原数据

/*
	*开始填充原文
	*/
	size_t n_len = ((len + 8) / 64) * 64 + 56 + 8;
	unsigned char*n_text = (unsigned char*)malloc(n_len);
	memset(n_text, 0x00, n_len);
	memcpy(n_text, text, len);
	n_text[len] = 0x80;
	unsigned char c_lens[8];
	memset(c_lens, 0x00, 8);
	unsigned int temp_len = (unsigned int)(len * 8);
	memcpy(c_lens, &temp_len, sizeof(unsigned long));
	/*
	*颠倒数据长度存储的端序
	*/
	for (int i = 7; i >= 4; i--)
	{
		int y = c_lens[i];
		c_lens[i] = c_lens[7-i];
		c_lens[7-i] = y;
	}
	memcpy(n_text + (n_len - 8), c_lens, 8);

这里要注意要将长度位置颠倒一下,把第一个字节放到最后一个,第二个放到倒二…以此类推,我不知道为什么SHA-1算法必须要这样处理,而MD5不需要,但经过我的测试,如果SHA-1不这么做最后结果是错误的

现在开始64字节一组开始计算

/*
	*分组计算,512bits一组
	*/
	for (int i = 0; i < n_len; i += 64)
	{
		//A=H[0],B=H[1],C=H[2],D=H[3],E=H[4]
		unsigned int H[5] = { 0,0,0,0,0 };
		unsigned char temp_text[64];

		memset(temp_text, 0x00, 64);
		memset(temp_ul_text, 0x00, 80*sizeof(unsigned int));
		memcpy(H, h, 5 * (sizeof(unsigned int)));
		memcpy(temp_text, (n_text + i), 64);

		CharToUlong(temp_text, temp_ul_text);

		for (int j = 0; j < 80; j++)
		{
			switch ((int)j/20)
			{
			case 0:
				k = 0x5a827999;
				f = (H[1] & H[2]) | ((~H[1]) & H[3]);
				break;
			case 1:
				k = 0x6ed9eba1;
				f = H[1] ^ H[2] ^ H[3];
				break;
			case 2:
				k = 0x8f1bbcdc;
				f = (H[1] & H[2]) | (H[1] & H[3]) | (H[2] & H[3]);
				break;
			case 3:
				k = 0xca62c1d6;
				f = H[1] ^ H[2] ^ H[3];
				break;
			default:
				break;
			}
			//ABCDE位置移动
			temp_A = ((H[0] << 5) | (H[0] >> 27)) + f + H[4] + temp_ul_text[j] + k;
			H[4] = H[3];
			H[3] = H[2];
			H[2] = (H[1] << 30) | (H[1] >> 2);
			H[1] = H[0];
			H[0] = temp_A;
		}
		//ABCDE累加
		for (int k = 0; k < 5; k++)
			h[k] += H[k];
	}

在这里还有一个CharToUlong函数,他的实现为:

inline void SHA1::CharToUlong
(
	_In_ const unsigned char* char_data,
	_Inout_ unsigned int* ulong_data
) 
{
	for (int i = 0; i < 16; i++)
	{
		int j = 4 * i;
		ulong_data[i] = ((unsigned int)char_data[j]) << 24 | 
						((unsigned int)char_data[1 + j]) << 16 | 
						((unsigned int)char_data[2 + j]) << 8 |
						((unsigned int)char_data[3 + j]) << 0;
	}
	for (int i = 16; i < 80; i++)
	{
		ulong_data[i] = ulong_data[i - 16] ^ ulong_data[i - 14] ^ 
						ulong_data[i - 8] ^ ulong_data[i - 3];
		ulong_data[i] = (ulong_data[i] << 1) | (ulong_data[i] >> 31);
	}
}

最后将我们之前申请的内存释放,并将结果转换为字符串即可

free(n_text);
	//字符串格式化返回输出结果
	for (int o = 0; o < 5; o++)
	{
		sprintf_s(outData, 40, "%08x", h[o]);
		outData += 8;
	}
	outData[40] = '\0';

完整代码地址:
Linux GCC:github
Windows MSVC:github

你可能感兴趣的:(C/C++,小菜鸡,自用备忘)