[Windows编程笔记]RSA加解密
2021-09-26 17:05 rnss 阅读(1174) 评论(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函数。