经典的加解密算法有AES,DES,RSA等,通过加密我们可以保护我们的数据。
windows自带加密库
windows提供了一组CryptoAPI来对用户的敏感私钥数据提供保护,并以灵活的方式对数据进行加密或签名。接下来介绍HASH值计算、AES加解密、RSA加解密。
CSP:加密服务提供程序
HASH值的计算
Hash算法是一种单向加密算法,HASH就是把任意长度的输入通过HASH算法变换成固定长度的输出,该值就是HASH值。HASH值的空间远小于输入值的空间,不同的输入可能得到相同的输出,所以无法利用HASH值来确定唯一的输入值。基于这种特性,HASH值一般用来执行数据完整性校验。如MD5,SHA等。
实现过程
//用于获取指定CSP内特定密钥容器的句柄。
BOOL CryptAcquireContext(
HCRYPTPROV *phProv,//指向csp句柄的指针,通过CryptReleaseContext释放。
LPCSTR szContainer,//密钥容器名称,一般设置NULL
LPCSTR szProvider,//包含要使用CSP名称的空终止字符串,一般设置NULL。
DWORD dwProvType,//指定要获取提供程序的类型,如PROV_RSA_AES支持RSA签名算法,AES加密算法以及HASH算法。
DWORD dwFlags);//标志值,一般设置0。
//创建一个空HASH对象
BOOL CryptCreateHash(
HCRYPTPROV hProv,//调用CryptAcquireContext创建的CSP句柄
ALG_ID Algid,//标识要使用的HASH算法的ALG_ID值。
HCRYPTKEY hKey,//对于非键控算法,设置为0.
DWORD dwFlags,//通常为0.
HCRYPTHASH *phHash);//HASH对象句柄。
//将数据添加到HASH对象,并进行HASH计算。
BOOL WINAPI CryptHashData(
HCRYPTHASH hHash, //HASH对象句柄
BYTE* pbData,//指向要添加到HASH对象数据缓冲区的指针
DWORD dwDataLen, //要添加到HASH对象缓冲区的指针
DWORD dwFlags//通常为0
);
//从HASH对象获取指定参数值
BOOL CRYPTFUNC CryptGethashParam(
HCRYPTHASH hHash,//HASH对象句柄
DWORD dwParam, //查询类型 HP_ALGID,HP_HASHSIZE,HP_HASHVAL。
BYTE *pbData, //指向接受指定值的数据缓冲区的指针。
DWORD *pdwDataLen, //指向指定的pdData缓冲区大小的DWORD值得指针,
DWORD dwFlags//0
);
//释放HASH对象句柄
BOOL CRYPTFUNC CryptDestroyHash(
HCRYPTHASH hHash
);
//释放CSP句柄
BOOL WINAPI CryptReleaseContext(
HCRYPTPROV hProv,
DWORD dwFlag
);
1 任何程序在使用CryptoAPI函数来执行数据计算或是加/解密之前,都需要调用CryptAcquireContext来获取CSP句柄。因为这里是计算数据HASH值,所以将提供程序类型设置为PROV_RSA_AES,该类型支持常用HASH算法。
2 调用CryptCreateHash在CSP中创建一个空的HASH对象并获取句柄,并可以指定HASH对象使用的HASH算法,如CALG_MD5表示MD5 HASH算法。
3 调用CryptGetHashParam从HASH对象获取指定参数,可以获取的参数有三种:
HP_ALGID:HASH算法
HP_HASHSIZE:HASH值的数据长度
HP_HASHVAL:HASH值
因为HASH算法不同,HASH值大小不固定,所以在获取HASH值之前,需要先获取HASH值长度,并申请足够的缓冲区存放HASH值。
编码实现
/*
Function:HASH值计算
pData:需要计算的内容
dwDataLength:内容长度
algHashType:HASH算法类型 如CALG_MD5,CALG_SHA1,CALG_SHA256等
ppHashData:存放计算的的HASH值
pdwHashDataLength:计算的HASH值长度
*/
BOOL CalculateHash(BYTE *pData, DWORD dwDataLength, ALG_ID algHashType, BYTE **ppHashData, DWORD *pdwHashDataLength)
{
HCRYPTPROV hCryptProv = NULL;//CSP句柄
HCRYPTHASH hCryptHash = NULL;//HASH对象句柄
BYTE *pHashData = NULL;
DWORD dwHashDataLength = 0;
DWORD dwTemp = 0;
BOOL bRet = FALSE;
do
{
// 获得指定CSP的密钥容器的句柄
bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
if (FALSE == bRet)
{
ShowError("CryptAcquireContext");
break;
}
// 创建一个HASH对象, 指定HASH算法
bRet = ::CryptCreateHash(hCryptProv, algHashType, NULL, NULL, &hCryptHash);
if (FALSE == bRet)
{
ShowError("CryptCreateHash");
break;
}
// 计算HASH数据
bRet = ::CryptHashData(hCryptHash, pData, dwDataLength, 0);
if (FALSE == bRet)
{
ShowError("CryptHashData");
break;
}
// 获取HASH结果的大小
dwTemp = sizeof(dwHashDataLength);
bRet = ::CryptGetHashParam(hCryptHash, HP_HASHSIZE, (BYTE *)(&dwHashDataLength), &dwTemp, 0);
if (FALSE == bRet)
{
ShowError("CryptGetHashParam");
break;
}
// 申请内存
pHashData = new BYTE[dwHashDataLength];
if (NULL == pHashData)
{
bRet = FALSE;
ShowError("new");
break;
}
::RtlZeroMemory(pHashData, dwHashDataLength);
// 获取HASH结果数据
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;
}
测试结果
AES加解密
AES是最常见的对称加密算法,所谓对称加密算法就是加密和解密使用相同密钥的加密算法。AES为分组密码,就是把明文分成一组一组的,每组长度相同,每次加密一组数据,直到加密完整个明文。AES对称加密算法的优势在于算法公开,计算量小,加密效率高。
在AES标准规范中,分组长度只能是128位(16个字节)。密钥长度可以是:
128:CALG_AES_128(16字节)
192:CALG_AES_192(24字节)
256:CALG_AES_256(32字节)
实现过程
//CryptDeriveKey:生成从基础数据值(可以是密码或者别的)派生出的加密会话密钥。
BOOL CRYPTFUNC CryptDeriveKey(
HCRYPTPROV hProv,//CSP句柄
ALG_ID Algid,//标识要使用的HASH算法的ALG_ID值
HCRYPTHASH hBaseData,//HASH对象句柄
DWORD dwFlags,//指定生成密钥的类型
HCRYPTKEY* phKey);//加密会话密钥
//对数据进行加密
BOOL CRYPTFUNC CryptEncrypt
(HCRYPTKEY hKey,//加密会话密钥
HCRYPTHASH hHash,//HASH对象句柄
BOOL Final,//指定它是否是加密系列中的最后一部分
DWORD dwFlags, //0
BYTE* pbData,//指向要加密的明文缓冲区的指针,该缓冲区文本会被生成的密文所覆盖。
DWORD* pdwDataLen,//pbData缓冲区中明文长度
DWORD dwBufLen//指定输入pbData缓冲区的总大小
);
BOOL CryptDecrypt(
HCRYPTKEY hKey,//加密会话密钥
HCRYPTHASH hHash,//HASH对象句柄
BOOL Final,//指定它是否是加密系列中的最后一部分
DWORD dwFlags,//0
BYTE *pbData,//指向要解密的明文缓冲区的指针,该缓冲区文本会被生成的明文所覆盖。
DWORD *pdwDataLen//pbData缓冲区的总大小
);
//释放密钥句柄
BOOL CRYPTFUNC CryptDestroyKey(HCRYPTKEY hKey);
1 任何程序在使用CryptoAPI函数来执行数据计算或是加/解密之前,都需要调用CryptAcquireContext来获取CSP句柄.由于这里使用的是AES对称加密算法,所以将提供程序类型设置为PROV_RSA_AES,该类型支持AES算法。
2 下面并不直接使用明文密码作为AES的加密密码,而是把明文密码的MD5值作为密码,再调用CryptDeriveKey函数来派生出AES的加密密钥。先CryptCreateHash创建HASH对象,然后CryptHashData对传入的密码进行MD5值计算,再CryptDeriveKey通过MD5来派生密钥,CALG_AES_128表示用于128位AES加密。
3 派生完密钥之后,可以调用CryptEncrypt来根据派生密钥中指定的加密算法进行加密。
AES加密编码实现
/*
Function:AES加密
pPassword:加密密码
dwPasswordLength:密码长度
pData:指向要加密的明文缓冲区的指针
dwDataLength:pData缓冲区中明文的长度
dwBufferLength:pData缓冲区总大小:大于明文长度,因为加密完成后密文也会写道这个缓冲区。
*/
BOOL AesEncrypt(BYTE *pPassword, DWORD dwPasswordLength, BYTE *pData, DWORD &dwDataLength, DWORD dwBufferLength)
{
BOOL bRet = TRUE;
HCRYPTPROV hCryptProv = NULL;//CSP句柄
HCRYPTHASH hCryptHash = NULL;//HASH对象句柄
HCRYPTKEY hCryptKey = NULL;//加密密钥句柄
do
{
// 获取CSP句柄
//CryptAcquireContext:用于获取指定CSP内特定密钥容器的句柄。
bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
if (FALSE == bRet)
{
ShowError("CryptAcquireContext");
break;
}
// 创建HASH对象
bRet = ::CryptCreateHash(hCryptProv, CALG_MD5, NULL, 0, &hCryptHash);
if (FALSE == bRet)
{
ShowError("CryptCreateHash");
break;
}
// 对密钥进行HASH计算
bRet = ::CryptHashData(hCryptHash, pPassword, dwPasswordLength, 0);
if (FALSE == bRet)
{
ShowError("CryptHashData");
break;
}
// 使用HASH来生成密钥
bRet = ::CryptDeriveKey(hCryptProv, CALG_AES_128, hCryptHash, CRYPT_EXPORTABLE, &hCryptKey);
if (FALSE == bRet)
{
ShowError("CryptDeriveKey");
break;
}
// 加密数据
//CryptEncrypt:由CSP模块保存的密钥指定的加密算法来加密数据。
//TRUE:表示该加密是AES加密数据中最后一组,这样系统会自动按照分组长度对数据进行填充并计算。
bRet = ::CryptEncrypt(hCryptKey, NULL, TRUE, 0, pData, &dwDataLength, dwBufferLength);
if (FALSE == bRet)
{
ShowError("CryptEncrypt");
break;
}
} while (FALSE);
// 关闭释放
if (hCryptKey)
{
::CryptDestroyKey(hCryptKey);//释放密钥句柄
}
if (hCryptHash)
{
::CryptDestroyHash(hCryptHash);//释放HASH对象句柄
}
if (hCryptProv)
{
::CryptReleaseContext(hCryptProv, 0);//释放CSP句柄
}
return bRet;
}
由于AES是对称加密,所以加密解密都是同一个密码。为了获取相同的解密密钥,需要根据明文密码来计算出派生密钥。过程与上面相同。
AES解码编码实现
/*
Function:AES解密
pPassword:解密密码
dwPasswordLength:密码长度
pData:指向要解密的明文缓冲区的指针
dwDataLength:pData缓冲区中明文的长度
dwBufferLength:pData缓冲区总大小:大于明文长度,因为加密完成后密文也会写道这个缓冲区。
*/
BOOL AesDecrypt(BYTE *pPassword, DWORD dwPasswordLength, BYTE *pData, DWORD &dwDataLength, DWORD dwBufferLength)
{
BOOL bRet = TRUE;
HCRYPTPROV hCryptProv = NULL;
HCRYPTHASH hCryptHash = NULL;
HCRYPTKEY hCryptKey = NULL;
do
{
// 获取CSP句柄
bRet = ::CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
if (FALSE == bRet)
{
ShowError("CryptAcquireContext");
break;
}
// 创建HASH对象
bRet = ::CryptCreateHash(hCryptProv, CALG_MD5, NULL, 0, &hCryptHash);
if (FALSE == bRet)
{
ShowError("CryptCreateHash");
break;
}
// 对密钥进行HASH计算
bRet = ::CryptHashData(hCryptHash, pPassword, dwPasswordLength, 0);
if (FALSE == bRet)
{
ShowError("CryptHashData");
break;
}
// 使用HASH来生成密钥
bRet = ::CryptDeriveKey(hCryptProv, CALG_AES_128, hCryptHash, CRYPT_EXPORTABLE, &hCryptKey);
if (FALSE == bRet)
{
ShowError("CryptDeriveKey");
break;
}
// 解密数据
bRet = ::CryptDecrypt(hCryptKey, NULL, TRUE, 0, pData, &dwDataLength);
if (FALSE == bRet)
{
ShowError("CryptDecrypt");
break;
}
} while (FALSE);
// 关闭释放
if (hCryptKey)
{
::CryptDestroyKey(hCryptKey);
}
if (hCryptHash)
{
::CryptDestroyHash(hCryptHash);
}
if (hCryptProv)
{
::CryptReleaseContext(hCryptProv, 0);
}
return bRet;
}
测试结果
RSA加解密
RSA是一种非对称加密算法,加密密钥和解密密钥不相同。RSA算法的安全性非常高,极大整数进行因数分解的难度决定了RSA算法的可靠性。但是由于RSA进行的都是大数运算,所以RSA速度很慢,是同安全级别的对称密码算法的1/1000左右,一般RSA用于少量数据加密。
实现过程
//随机生成加密会话密钥或公钥/私钥对,密钥或密钥对的句柄在phKey返回。
BOOL CRYPTFUNC CryptGenKey(
HCRYPTPROV hProv,//CSP句柄
ALG_ID Algid,//标识要使用的HASH算法的ALG_ID值
DWORD dwFlags,//指定生成的密钥类型
HCRYPTKEY* phKey//密钥句柄
);
//以安全的方式从加密服务提供程序中导出加密密钥或密钥对。
BOOL CRYPTFUNC CryptExportKey(
HCRYPTKEY hKey,//指向要导出的密钥句柄
HCRYPTKEY hExpKey,//指向目标用户的密钥句柄,通常为NULL
DWORD dwBlobType,//指定要在pdData中导出键BLOB的类型。PUBLICKEYBLOB表示导出公钥,PRIVATEKEYBLOB表示导出私钥。
DWORD dwFlags,//0
BYTE* pbData,//指向接收关键BLOB数据的缓冲区的指针
DWORD* pdwbDataLen//pddate缓冲区大小
);
//将加密密钥从密钥 BLOB传输到加密服务提供程序(CSP)
BOOL CryptImportKey(
HCRYPTPROV hProv,//CSP句柄
const BYTE *pbData,//指定一个BYTE数组,此密钥BOLO由CryptExportKey创建
DWORD dwDataLen,//包含关键BLOB长度
HCRYPTKEY hPubKey,//解密存储在pdData中的加密密钥句柄
DWORD dwFlags,//目前仅在将PRIVATEKEYBLOB形式的公钥/私钥对导入 CSP 时使用
HCRYPTKEY *phKey//指向接受导入键句柄的指针
);
通常情况下,公钥用来加密数据,私钥用来解密数据,因此在数据加解/密前,需要先产生公钥和私钥密钥对。公钥和私钥成对,使用公钥加密的数据只能用唯一的私钥来解密。
1 首先,任何程序在使用CryptoAPI函数来执行数据计算或是加/解密之前,都需要调用CryptAcquireContext来获取CSP句柄.由于这里使用的是RSA非对称加密算法,所以将提供程序类型设置为PROV_RSA_FULL,该类型支持RSA算法。
2 调用CryptGenKey函数随机生成AT_KEYEXCHANGE交换密钥对,并设置生成的密钥对类型为CRYPT_EXPORTABLE,它是可导出的,可以使用CryptExportKey导出密钥。此时RSA密钥对已经生成。
编码实现
/*
Function:生成公钥和私钥
ppPublicKey:公钥
pdwPublicKeyLength:公钥长度
ppPrivateKey:私钥
pdwPrivateKeyLength:私钥长度
*/
BOOL GenerateKey(BYTE **ppPublicKey, DWORD *pdwPublicKeyLength, BYTE **ppPrivateKey, DWORD *pdwPrivateKeyLength)
{
BOOL bRet = TRUE;
HCRYPTPROV hCryptProv = NULL;//CSP句柄
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;
}
/*
Function:公钥加密数据
*/
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;
}
/*
Function:私钥解密数据
*/
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;
}