轻松看懂的加解密系列(3) —— “哈希算法”大PK

图-1

        在做了两期加解密系列之后,本章让我们换个话题——“哈希”。

        先从“信息技术世界的需求”谈起。
  • 当接收方收到一份来自发送方的文件后,如何快速验证文件内容没有丢失、乱序或者被篡改?
  • 现实世界里海量的文献如何能被映射成简短而唯一的“索引”,从而方便快速归档和查找?
  • 如何做到不把明文密码存储到第三方验证机构,但又可以在第三方安全地验证密码?
  • 对于一份电子合同文本来说,如何产生一个消息摘要来确保消息的完整性和真实性?

        加解密的方式显然无法满足以上这些需求,这就需要数据安全领域的另一大概念“哈希”出场了。在谈到哈希算法时,书本上给出的定义通常会包括,哈希算法是一种将输入数据转换为固定长度的数字串(哈希值)的数学函数。哈希值是唯一的,即使输入数据微小地变化,哈希值也会截然不同。哈希运算本身不被视为一种加密(Encryption)操作,而是一种散列(Hashing)操作【图-1】。虽然哈希和加密都涉及数据转换,但它们有本质上不同的目标和特性:

图-2

哈希(Hashing):

  • 目标:哈希算法的主要目标是将输入数据(原文)转换为一个固定长度的哈希值(散列值)【图-2】。
  • 特性:哈希算法是单向的,不可逆的,即无法从哈希值还原出原始数据。它旨在生成唯一的哈希值,以便验证数据完整性、生成数据的唯一标识符以及用于快速数据检索等。
  • 典型用途:数据完整性验证、数字签名、密码存储、数据索引等。

加密(Encryption):

  • 目标:加密算法的主要目标是将原始数据(明文)转换为经过加密的数据(密文),以便保护数据的机密性,只有授权用户可以解密和访问数据。
  • 特性:加密算法是可逆的,只有掌握正确的密钥才能解密密文还原为明文。它的目标是提供保密性和隐私保护。
  • 典型用途:数据传输加密、文件加密、通信加密、身份验证等。

        通常,这两种技术可以结合使用,以提供更全面的数据安全性,例如,首先对数据进行哈希以验证完整性,然后对数据进行加密以保护隐私和保密性。

四种不同哈希算法实例及讲解:
int main()
{
	BYTE* pData = NULL;
	DWORD dwDataLength = 0;
	DWORD i = 0;
	BYTE* pHashData = NULL;
	DWORD dwHashDataLength = 0;

	// Read target file
	GetFileData("..\\readMe.txt", &pData, &dwDataLength);

	// MD5
	CalculateHash(pData, dwDataLength, CALG_MD5, &pHashData, &dwHashDataLength);
	printf("MD5[%d]\n", dwHashDataLength);
	for (i = 0; i < dwHashDataLength; i++)
	{
		printf("%x", pHashData[i]);
	}
	printf("\n\n", dwHashDataLength);
	if (pHashData)
	{
		delete[]pHashData;
		pHashData = NULL;
	}

	// SHA1 and printout omitted here
	CalculateHash(pData, dwDataLength, CALG_SHA1, &pHashData, &dwHashDataLength);

	// SHA256 and printout omitted here
	CalculateHash(pData, dwDataLength, CALG_SHA_256, &pHashData, &dwHashDataLength);

	// SHA512 and printout omitted here
	CalculateHash(pData, dwDataLength, CALG_SHA_512, &pHashData, &dwHashDataLength);

	system("pause");
	return 0;
}

【讲解】以上 main 函数首先对目标文件内容进行读取,然后用封装了的计算哈希函数来进行哈希运算。 封装了的函数有5个参数,分别是目标数据的缓冲区指针和内容长度,输出哈希后的数据指针及其长度,以及将要采用的哈希算法。本例实验了如下四种哈希算法,具体参数为CALG_MD5、CALG_SHA1、CALG_SHA_256 和 CALG_SHA_512:

1. MD5(Message Digest Algorithm 5):

  • 优点:计算速度较快,产生128位(16字节)的哈希值。
  • 缺点:安全性较差,容易受到碰撞攻击(即找到两个不同的输入,它们产生相同的哈希值)。

2. SHA-1(Secure Hash Algorithm 1):

  • 优点:较快,广泛使用,产生160位(20字节)的哈希值。
  • 缺点:安全性逐渐降低,容易受到碰撞攻击,不再被推荐用于安全应用。

3. SHA-256(Secure Hash Algorithm 256位):

  • 优点:较高的安全性,较长的哈希值256位(32字节),广泛用于数字签名和数据完整性验证。
  • 缺点:计算速度相对较慢。

4. SHA-512(Secure Hash Algorithm 512位):

  • 优点:更长的哈希值512位(64字节),更高的安全性。
  • 缺点:计算速度相对较慢,对于一般应用可能过于冗长。
图-3 程序执行后的四种不同哈希算法的输出

BOOL CalculateHash(BYTE* pData, DWORD dwDataLength, ALG_ID algHashType, BYTE** ppHashData, DWORD* pdwHashDataLength)
{
	HCRYPTPROV hCryptProv = NULL;
	HCRYPTHASH hCryptHash = NULL;
	BYTE* pHashData = NULL;
	DWORD dwHashDataLength = 0;
	DWORD dwTemp = 0;
	BOOL bRet = FALSE;


	do
	{
		// Gets the handle to the key container of the specified CSP
		bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
		if (FALSE == bRet)
		{
			ShowError("CryptAcquireContext");
			break;
		}

		// Create a HASH object and specify the HASH algorithm
		bRet = ::CryptCreateHash(hCryptProv, algHashType, NULL, NULL, &hCryptHash);
		if (FALSE == bRet)
		{
			ShowError("CryptCreateHash");
			break;
		}

		// Calculate HASH data
		bRet = ::CryptHashData(hCryptHash, pData, dwDataLength, 0);
		if (FALSE == bRet)
		{
			ShowError("CryptHashData");
			break;
		}

		// Get the size of the HASH result
		dwTemp = sizeof(dwHashDataLength);
		bRet = ::CryptGetHashParam(hCryptHash, HP_HASHSIZE, (BYTE*)(&dwHashDataLength), &dwTemp, 0);
		if (FALSE == bRet)
		{
			ShowError("CryptGetHashParam");
			break;
		}

		// Apply for memory
		pHashData = new BYTE[dwHashDataLength];
		if (NULL == pHashData)
		{
			bRet = FALSE;
			ShowError("new");
			break;
		}
		::RtlZeroMemory(pHashData, dwHashDataLength);

		// Get HASH result data
		bRet = ::CryptGetHashParam(hCryptHash, HP_HASHVAL, pHashData, &dwHashDataLength, 0);
		if (FALSE == bRet)
		{
			ShowError("CryptGetHashParam");
			break;
		}

		*ppHashData = pHashData;
		*pdwHashDataLength = dwHashDataLength;

	} while (FALSE);

	if (FALSE == bRet)
	{
		if (pHashData)
		{
			delete[]pHashData;
			pHashData = NULL;
		}
	}
	if (hCryptHash)
	{
		::CryptDestroyHash(hCryptHash);
	}
	if (hCryptProv)
	{
		::CryptReleaseContext(hCryptProv, 0);
	}

	return bRet;
}

【讲解】Wincrypt APIs 哈希算法的程序与加解密类似,也是按照 CryptAcquireContext(创建 CSP)、CryptCreateHash(创建哈希对象)和 CryptHashData(计算哈希)的顺序来调用不同的接口。如果顺利的话,再两次调用 CryptGetHashParam,分别通过指定 HP_HASHSIZE 和 HP_HASHVAL,来得到哈希后数据的长度,然后申请对应缓冲区大小,最后塞入哈希后的数据。【图-4】展示的是 MD5 算法下,哈希后数据的长度(16)和内容(内存里的哈希数据与控制台输出【图-3】的完全一致)

图-4

 实验环节:
  1. 测试两台电脑分别对同样的原文运行程序后是否有一样的输出,结果是得到完全一样的输出。如【图-5】
    图-5
  2. 原文中有4KB大小,仅仅篡改了一个标点符号后的哈希结果区别【图-6】。这就是开篇引用的定义中所提及的“哈希值是唯一的,即使输入数据微小地变化,哈希值也会截然不同”。
    图-6
  3. 对于同一句话内容,C++程序与网页版的哈希计算相比较【图-7】。可以看到只要采用相同的哈希算法,不管在任何平台,用任何语言来实现,一样的输入数据都应该能得到一样的哈希后的输出数据。
图-7
写在最后的总结:

        在完整调试了一个哈希算法实例之后,此刻是在大脑里复盘哈希算法的好时机,这有助于我们深刻理解哈希的概念和用途,而以下内容刚好与文章开头所提“信息技术世界的需求”相呼应。

  • 数据完整性验证、文件完整性检查:哈希值充当数据的唯一摘要,它们是数据内容的固定长度表示。当数据传输或存储时,接收方可以重新计算哈希值,并与发送方提供的哈希值进行比较。如果哈希值匹配,说明数据在传输或存储过程中没有被篡改,从而验证了数据的完整性。
  • 数据索引和检索的唯一标识:哈希值可用于加速数据检索过程。数据库和文件系统中,对大段内容常常使用哈希索引,以快速查找和访问数据。
  • 密码存储:哈希值常用于存储用户密码。而不是将密码明文存储在数据库中,通常会存储其哈希值。当用户尝试登录时,系统将用户提供的密码进行哈希,然后与存储的哈希值进行比较。
  • 数字签名:哈希值常用于数字签名过程中。发送方可以使用私钥对数据的哈希值进行签名,接收方可以使用发送方的公钥来验证签名。这确保了数据的真实性和来源。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值