代码改变世界

[Windows编程笔记]RSA加解密

2021-09-26 17:05  rnss  阅读(1016)  评论(0编辑  收藏  举报

《Windows黑客编程技术详解》学习笔记

本文代码均来自https://www.jb51.net/books/755116.html

写了3个函数

生成公钥和私钥

// 生成公钥和私钥
BOOL GenerateKey(BYTE **ppPublicKey, DWORD *pdwPublicKeyLength, BYTE **ppPrivateKey, DWORD *pdwPrivateKeyLength)
{
	BOOL bRet = TRUE;
	HCRYPTPROV hCryptProv = NULL;
	HCRYPTKEY hCryptKey = NULL;
	BYTE *pPublicKey = NULL;
	DWORD dwPublicKeyLength = 0;
	BYTE *pPrivateKey = NULL;
	DWORD dwPrivateKeyLength = 0;

	do
	{
		// 获取CSP句柄
		bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
		if (FALSE == bRet)
		{
			ShowError("CryptAcquireContext");
			break;
		}

		// 生成公私密钥对
		bRet = ::CryptGenKey(hCryptProv, AT_KEYEXCHANGE, CRYPT_EXPORTABLE, &hCryptKey);
		if (FALSE == bRet)
		{
			ShowError("CryptGenKey");
			break;
		}

		// 获取公钥密钥的长度和内容
		bRet = ::CryptExportKey(hCryptKey, NULL, PUBLICKEYBLOB, 0, NULL, &dwPublicKeyLength);
		if (FALSE == bRet)
		{
			ShowError("CryptExportKey");
			break;
		}
		pPublicKey = new BYTE[dwPublicKeyLength];
		::RtlZeroMemory(pPublicKey, dwPublicKeyLength);
		bRet = ::CryptExportKey(hCryptKey, NULL, PUBLICKEYBLOB, 0, pPublicKey, &dwPublicKeyLength);
		if (FALSE == bRet)
		{
			ShowError("CryptExportKey");
			break;
		}

		// 获取私钥密钥的长度和内容
		bRet = ::CryptExportKey(hCryptKey, NULL, PRIVATEKEYBLOB, 0, NULL, &dwPrivateKeyLength);
		if (FALSE == bRet)
		{
			ShowError("CryptExportKey");
			break;
		}
		pPrivateKey = new BYTE[dwPrivateKeyLength];
		::RtlZeroMemory(pPrivateKey, dwPrivateKeyLength);
		bRet = ::CryptExportKey(hCryptKey, NULL, PRIVATEKEYBLOB, 0, pPrivateKey, &dwPrivateKeyLength);
		if (FALSE == bRet)
		{
			ShowError("CryptExportKey");
			break;
		}

		// 返回数据
		*ppPublicKey = pPublicKey;
		*pdwPublicKeyLength = dwPublicKeyLength;
		*ppPrivateKey = pPrivateKey;
		*pdwPrivateKeyLength = dwPrivateKeyLength;

	} while (FALSE);

	// 释放关闭
	if (hCryptKey)
	{
		::CryptDestroyKey(hCryptKey);
	}
	if (hCryptProv)
	{
		::CryptReleaseContext(hCryptProv, 0);
	}

	return bRet;
}

具体流程如下

首先,调用CryptAcquireContext函数来获取加密服务提供程序所需的CSP句柄。由于本程序实现的是使用RSA非对称加密算法加/解密数据,所以将提供程序类型设置为PROV_RSA_FULL,该类型支持RSA非对称加密算法。需要注意的是最后一个参数dwFlags需要设置为CRYPT_VERIFYCONTEXT,如果设置为0的话运行程序会报错,原因未知。

// 获取CSP句柄
bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);

然后,可以直接调用CryptGenKey函数随机生成AT_KEYEXCHANGE交换密钥对,并设置生成的密钥对类型为CRYPT_EXPORTABLE,它是可导出的,可以使用CryptExportKey函数导出密钥。

// 随机生成公钥/私钥对
BOOL CryptGenKey(
  HCRYPTPROV hProv,  // CryptAcquireContext创建的CSP句柄
  ALG_ID     Algid,  // AT_KEYEXCHANGE表示生成的是交换密钥对
  DWORD      dwFlags,  // CRYPT_EXPORTABLE表示密钥可导出,可以使用CryptExportKey函数导出密钥
  HCRYPTKEY  *phKey  // 返回的密钥对句柄
);
// 生成公私密钥对
bRet = ::CryptGenKey(hCryptProv, AT_KEYEXCHANGE, CRYPT_EXPORTABLE, &hCryptKey);

经过上述两步操作后,RSA密钥对就已经成功生成。但是,为了方便后续使用公钥和私钥密钥对,需要通过CryptExportKey函数来导出密钥。由于密钥长度都不是固定的,所以在获取密钥之前,应该先确定密钥的长度。将输出缓冲区置为NULL(即可返回实际所需的缓冲区大小),这样可以申请足够的密钥缓冲区。若要导出公钥,则要将导出类型置为PUBLICKEYBLOB;若要导出私钥,则要将导出类型置为PRIVATEKEYBLOB。

// 导出密钥对
BOOL CryptExportKey(
  HCRYPTKEY hKey,  // 要导出的密钥句柄
  HCRYPTKEY hExpKey,  // NULL
  DWORD     dwBlobType,  // PUBLICKEYBLOB表示导出公钥,PRIVATEKEYBLOB表示导出私钥
  DWORD     dwFlags,  // 0
  BYTE      *pbData,  // 若要确定密钥长度,置为NULL,若要导出公私钥,置为接收公私钥的缓冲区
  DWORD     *pdwDataLen  // 在入口处包含pbData指向的缓冲区大小,返回时包含存储在pbData中的字节数
);
// 获取公钥密钥的长度和内容
bRet = ::CryptExportKey(hCryptKey, NULL, PUBLICKEYBLOB, 0, NULL, &dwPublicKeyLength);
bRet = ::CryptExportKey(hCryptKey, NULL, PUBLICKEYBLOB, 0, pPublicKey, &dwPublicKeyLength);

// 获取私钥密钥的长度和内容
bRet = ::CryptExportKey(hCryptKey, NULL, PRIVATEKEYBLOB, 0, NULL, &dwPrivateKeyLength);
bRet = ::CryptExportKey(hCryptKey, NULL, PRIVATEKEYBLOB, 0, pPrivateKey, &dwPrivateKeyLength);

最后,调用CryptDestroyKey函数来删除密钥的句柄,调用CryptReleaseContext函数释放CSP句柄。

// 释放关闭
if (hCryptKey)
{
	::CryptDestroyKey(hCryptKey);
}
if (hCryptProv)
{
	::CryptReleaseContext(hCryptProv, 0);
}

公钥加密数据

// 公钥加密数据
BOOL RsaEncrypt(BYTE *pPublicKey, DWORD dwPublicKeyLength, BYTE *pData, DWORD &dwDataLength, DWORD dwBufferLength)
{
	BOOL bRet = TRUE;
	HCRYPTPROV hCryptProv = NULL;
	HCRYPTKEY hCryptKey = NULL;

	do
	{
		// 获取CSP句柄
		bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
		if (FALSE == bRet)
		{
			ShowError("CryptAcquireContext");
			break;
		}

		// 导入公钥
		bRet = ::CryptImportKey(hCryptProv, pPublicKey, dwPublicKeyLength, NULL, 0, &hCryptKey);
		if (FALSE == bRet)
		{
			ShowError("CryptImportKey");
			break;
		}

		// 加密数据
		bRet = ::CryptEncrypt(hCryptKey, NULL, TRUE, 0, pData, &dwDataLength, dwBufferLength);
		if (FALSE == bRet)
		{
			ShowError("CryptImportKey");
			break;
		}

	} while (FALSE);

	// 释放并关闭
	if (hCryptKey)
	{
		::CryptDestroyKey(hCryptKey);
	}
	if (hCryptProv)
	{
		::CryptReleaseContext(hCryptProv, 0);
	}

	return bRet;
}

具体实现流程如下

首先,依然是通过调用CryptAcquireContext函数来获取加密程序所需的CSP句柄。将提供程序类型设置为PROV_RSA_FULL,最后一个参数dwFlags设置为CRYPT_VERIFYCONTEXT。

// 获取CSP句柄
bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);

其次,需要把公钥导入到CSP中以方便后续进行加密操作。调用CryptImportKey函数可将密钥导入CSP中,并获取导入的公钥密钥句柄。

// 将密钥从密钥BLOB导入到加密程序中
BOOL CryptImportKey(
  HCRYPTPROV hProv,  // CryptAcquireContext函数获取的CSP句柄
  const BYTE *pbData,  // CryptExportKey函数创建的密钥
  DWORD      dwDataLen,  // 密钥的长度
  HCRYPTKEY  hPubKey,  // NULL
  DWORD      dwFlags,  // 0
  HCRYPTKEY  *phKey  // 导入键的句柄
);
// 导入公钥
bRet = ::CryptImportKey(hCryptProv, pPublicKey, dwPublicKeyLength, NULL, 0, &hCryptKey);

最后,可以直接通过调用CryptEncrypt函数来对数据进行加密,由于数据的输入和密文的输出使用同一个缓冲区,所以一定要确保缓冲区足够大。因为RSA非对称加密算法也是一种分组加密算法,所以要指定Final参数为TRUE来指定加密数据是最后一组加密数据。

// 用来加密数据
BOOL CryptEncrypt(
  HCRYPTKEY  hKey,  // CryptImportKey函数导入的密钥
  HCRYPTHASH hHash,  // NULL
  BOOL       Final,  // TRUE
  DWORD      dwFlags,  // 0
  BYTE       *pbData,  // 要加密的明文,该缓冲区中的纯文本会被密文覆盖
  DWORD      *pdwDataLen,  // 入口处是要加密的明文长度,退出时是写入到pbData中的密文长度
  DWORD      dwBufLen  // 指定输入pbData缓冲区的总大小,注意:密文长度可能大于明文长度
);
// 加密数据
bRet = ::CryptEncrypt(hCryptKey, NULL, TRUE, 0, pData, &dwDataLength, dwBufferLength);

调用CryptDestroyKey函数来删除密钥的句柄,调用CryptReleaseContext函数释放CSP句柄。

// 释放并关闭
if (hCryptKey)
{
	::CryptDestroyKey(hCryptKey);
}
if (hCryptProv)
{
	::CryptReleaseContext(hCryptProv, 0);
}

私钥解密数据

// 私钥解密数据
BOOL RsaDecrypt(BYTE *pPrivateKey, DWORD dwProvateKeyLength, BYTE *pData, DWORD &dwDataLength)
{
	BOOL bRet = TRUE;
	HCRYPTPROV hCryptProv = NULL;
	HCRYPTKEY hCryptKey = NULL;

	do
	{
		// 获取CSP句柄
		bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
		if (FALSE == bRet)
		{
			ShowError("CryptAcquireContext");
			break;
		}

		// 导入私钥
		bRet = ::CryptImportKey(hCryptProv, pPrivateKey, dwProvateKeyLength, NULL, 0, &hCryptKey);
		if (FALSE == bRet)
		{
			ShowError("CryptImportKey");
			break;
		}

		// 解密数据
		bRet = ::CryptDecrypt(hCryptKey, NULL, TRUE, 0, pData, &dwDataLength);
		if (FALSE == bRet)
		{
			ShowError("CryptDecrypt");
			break;
		}

	} while (FALSE);

	// 释放并关闭
	if (hCryptKey)
	{
		::CryptDestroyKey(hCryptKey);
	}
	if (hCryptProv)
	{
		::CryptReleaseContext(hCryptProv, 0);
	}

	return bRet;
}

使用RSA私钥解密密文的实现流程与使用公钥加密数据的流程很相似。不同点是CryptImportKey函数的第二和第三个参数要设置为私钥和私钥的长度

// 导入私钥
bRet = ::CryptImportKey(hCryptProv, pPrivateKey, dwProvateKeyLength, NULL, 0, &hCryptKey);

解密时要用CryptDecrypt函数,Final参数也要设置为TRUE

// 用来解密数据
BOOL CryptDecrypt(
  HCRYPTKEY  hKey,  // 密钥句柄
  HCRYPTHASH hHash,  // NULL
  BOOL       Final,  // TRUE
  DWORD      dwFlags,  // 0
  BYTE       *pbData,  // 要解密的数据,解密完成后,明文会放回到该缓冲区
  DWORD      *pdwDataLen  // 调用此函数前,表示密文长度,返回时,表示明文长度
);
// 解密数据
bRet = ::CryptDecrypt(hCryptKey, NULL, TRUE, 0, pData, &dwDataLength);

完整代码

// CryptoApi_Rsa_Test.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <Windows.h>


void ShowError(char *pszText)
{
	char szErr[MAX_PATH] = { 0 };
	::wsprintf(szErr, "%s Error[0x%x]\n", pszText, ::GetLastError());
#ifdef _DEBUG
	::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
#endif
}


// 生成公钥和私钥
BOOL GenerateKey(BYTE **ppPublicKey, DWORD *pdwPublicKeyLength, BYTE **ppPrivateKey, DWORD *pdwPrivateKeyLength)
{
	BOOL bRet = TRUE;
	HCRYPTPROV hCryptProv = NULL;
	HCRYPTKEY hCryptKey = NULL;
	BYTE *pPublicKey = NULL;
	DWORD dwPublicKeyLength = 0;
	BYTE *pPrivateKey = NULL;
	DWORD dwPrivateKeyLength = 0;

	do
	{
		// 获取CSP句柄
		bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, 0);
		if (FALSE == bRet)
		{
			ShowError("CryptAcquireContext");
			break;
		}

		// 生成公私密钥对
		bRet = ::CryptGenKey(hCryptProv, AT_KEYEXCHANGE, CRYPT_EXPORTABLE, &hCryptKey);
		if (FALSE == bRet)
		{
			ShowError("CryptGenKey");
			break;
		}

		// 获取公钥密钥的长度和内容
		bRet = ::CryptExportKey(hCryptKey, NULL, PUBLICKEYBLOB, 0, NULL, &dwPublicKeyLength);
		if (FALSE == bRet)
		{
			ShowError("CryptExportKey");
			break;
		}
		pPublicKey = new BYTE[dwPublicKeyLength];
		::RtlZeroMemory(pPublicKey, dwPublicKeyLength);
		bRet = ::CryptExportKey(hCryptKey, NULL, PUBLICKEYBLOB, 0, pPublicKey, &dwPublicKeyLength);
		if (FALSE == bRet)
		{
			ShowError("CryptExportKey");
			break;
		}

		// 获取私钥密钥的长度和内容
		bRet = ::CryptExportKey(hCryptKey, NULL, PRIVATEKEYBLOB, 0, NULL, &dwPrivateKeyLength);
		if (FALSE == bRet)
		{
			ShowError("CryptExportKey");
			break;
		}
		pPrivateKey = new BYTE[dwPrivateKeyLength];
		::RtlZeroMemory(pPrivateKey, dwPrivateKeyLength);
		bRet = ::CryptExportKey(hCryptKey, NULL, PRIVATEKEYBLOB, 0, pPrivateKey, &dwPrivateKeyLength);
		if (FALSE == bRet)
		{
			ShowError("CryptExportKey");
			break;
		}

		// 返回数据
		*ppPublicKey = pPublicKey;
		*pdwPublicKeyLength = dwPublicKeyLength;
		*ppPrivateKey = pPrivateKey;
		*pdwPrivateKeyLength = dwPrivateKeyLength;

	} while (FALSE);

	// 释放关闭
	if (hCryptKey)
	{
		::CryptDestroyKey(hCryptKey);
	}
	if (hCryptProv)
	{
		::CryptReleaseContext(hCryptProv, 0);
	}

	return bRet;
}


// 公钥加密数据
BOOL RsaEncrypt(BYTE *pPublicKey, DWORD dwPublicKeyLength, BYTE *pData, DWORD &dwDataLength, DWORD dwBufferLength)
{
	BOOL bRet = TRUE;
	HCRYPTPROV hCryptProv = NULL;
	HCRYPTKEY hCryptKey = NULL;

	do
	{
		// 获取CSP句柄
		bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, 0);
		if (FALSE == bRet)
		{
			ShowError("CryptAcquireContext");
			break;
		}

		// 导入公钥
		bRet = ::CryptImportKey(hCryptProv, pPublicKey, dwPublicKeyLength, NULL, 0, &hCryptKey);
		if (FALSE == bRet)
		{
			ShowError("CryptImportKey");
			break;
		}

		// 加密数据
		bRet = ::CryptEncrypt(hCryptKey, NULL, TRUE, 0, pData, &dwDataLength, dwBufferLength);
		if (FALSE == bRet)
		{
			ShowError("CryptImportKey");
			break;
		}

	} while (FALSE);

	// 释放并关闭
	if (hCryptKey)
	{
		::CryptDestroyKey(hCryptKey);
	}
	if (hCryptProv)
	{
		::CryptReleaseContext(hCryptProv, 0);
	}

	return bRet;
}


// 私钥解密数据
BOOL RsaDecrypt(BYTE *pPrivateKey, DWORD dwProvateKeyLength, BYTE *pData, DWORD &dwDataLength)
{
	BOOL bRet = TRUE;
	HCRYPTPROV hCryptProv = NULL;
	HCRYPTKEY hCryptKey = NULL;

	do
	{
		// 获取CSP句柄
		bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, 0);
		if (FALSE == bRet)
		{
			ShowError("CryptAcquireContext");
			break;
		}

		// 导入私钥
		bRet = ::CryptImportKey(hCryptProv, pPrivateKey, dwProvateKeyLength, NULL, 0, &hCryptKey);
		if (FALSE == bRet)
		{
			ShowError("CryptImportKey");
			break;
		}

		// 解密数据
		bRet = ::CryptDecrypt(hCryptKey, NULL, TRUE, 0, pData, &dwDataLength);
		if (FALSE == bRet)
		{
			ShowError("CryptDecrypt");
			break;
		}

	} while (FALSE);

	// 释放并关闭
	if (hCryptKey)
	{
		::CryptDestroyKey(hCryptKey);
	}
	if (hCryptProv)
	{
		::CryptReleaseContext(hCryptProv, 0);
	}

	return bRet;
}



int _tmain(int argc, _TCHAR* argv[])
{
	BYTE *pPublicKey = NULL;
	DWORD dwPublicKeyLength = 0;
	BYTE *pPrivateKey = NULL;
	DWORD dwPrivateKeyLength = 0;
	BYTE *pData = NULL;
	DWORD dwDataLength = 0;
	DWORD dwBufferLength = 4096;
	DWORD i = 0;

	pData = new BYTE[dwBufferLength];
	if (NULL == pData)
	{
		return 1;
	}
	::RtlZeroMemory(pData, dwBufferLength);
	::lstrcpy((char *)pData, "What is your name? DemonGan");
	dwDataLength = 1 + ::lstrlen((char *)pData);
	printf("Text[%d]\n", dwDataLength);
	for (i = 0; i < dwDataLength; i++)
	{
		printf("%x", pData[i]);
	}
	printf("\n\n");

	// 生成公钥和私钥
	GenerateKey(&pPublicKey, &dwPublicKeyLength, &pPrivateKey, &dwPrivateKeyLength);
	printf("Public Key[%d]\n", dwPublicKeyLength);
	for (i = 0; i < dwPublicKeyLength; i++)
	{
		printf("%.2x", pPublicKey[i]);
	}
	printf("\n");
	printf("Private Key[%d]\n", dwPrivateKeyLength);
	for (i = 0; i < dwPrivateKeyLength; i++)
	{
		printf("%.2x", pPrivateKey[i]);
	}
	printf("\n\n");

	// 公钥加密
	RsaEncrypt(pPublicKey, dwPublicKeyLength, pData, dwDataLength, dwBufferLength);
	printf("RSA Encrypt[%d]\n", dwDataLength);
	for (i = 0; i < dwDataLength; i++)
	{
		printf("%x", pData[i]);
	}
	printf("\n\n");

	// 私钥解密
	RsaDecrypt(pPrivateKey, dwPrivateKeyLength, pData, dwDataLength);
	printf("RSA Decrypt[%d]\n", dwDataLength);
	for (i = 0; i < dwDataLength; i++)
	{
		printf("%x", pData[i]);
	}
	printf("\n\n");

	// 释放
	if (pData)
	{
		delete[]pData;
		pData = NULL;
	}
	if (pPrivateKey)
	{
		delete[]pPrivateKey;
		pPrivateKey = NULL;
	}
	if (pPublicKey)
	{
		delete[]pPublicKey;
		pPublicKey = NULL;
	}

	system("pause");
	return 0;
}

小结

生成公钥和私钥:调用CryptAcquireContext函数获取CSP句柄,调用CryptGenKey函数随机生成AT_KEYEXCHANGE交换密钥对,通过CryptExportKey函数导出公钥和私钥。由于密钥长度并不是固定的,所以需要先确定密钥长度的大小,以申请足够的密钥存放缓冲区。最后,调用CryptDestroyKey函数来删除密钥的句柄,调用CryptReleaseContext函数释放CSP句柄。

公钥加密数据:通过调用CryptAcquireContext函数来获取加密程序所需的CSP句柄,调用CryptImportKey函数将密钥导入CSP中,并获取导入的公钥密钥句柄,调用CryptEncrypt函数来对数据进行加密,最后,调用CryptDestroyKey函数来删除密钥的句柄,调用CryptReleaseContext函数释放CSP句柄。

私钥解密数据:和公钥加密数据流程基本相同,除了CryptImportKey函数的第二和第三个参数要设置为私钥和私钥的长度,以及解密时要用CryptDecrypt函数。