最近有需要计算SHA256的需求,于是在原有的基础上做了修改,唯一需要注意的地方就是在调用CryptAcquireContext需要修改参数如下:
CryptAcquireContext(&hCryptProv, NULL, NULL,
PROV_RSA_AES, CRYPT_VERIFYCONTEXT)
更新之后的效果图:
--------------------------------------------------------------------------------------
之前偶尔看到了一篇论文里面提到微软的Crypto
API库,可以实现加解密、Hash运算、密钥交换、证书认证等功能,于是就萌生了用该库实现DES、AES、RSA、MD5等算法的想法(以前都是人工实现,太麻烦了……)。
这里先介绍各种Hash算法的实现方法:
Step0:
环境配置,我这里用的是vs2010,貌似只需要添加#include
头文件和#pragma
comment(lib, "Crypt32.lib")代码就可以使用这些API了,不需要进行额外的工程设置。
Step1:
首先我们先看看这个函数,CryptHashData(看起来很靠谱的样子,其实只算一个中间函数吧。)
BOOL WINAPI
CryptHashData(HCRYPTHASH
hHash,
BYTE *pbData,
//带哈希数据首地址
DWORD dwDataLen,//待哈希数据长度
DWORD dwFlags
);
参数:
hHash
[in]哈希对象句柄
pbData
[in]指向要加入到哈希对象的数据指针
dwDataLen
[in]数据长度
dwFlags
[in]标志
CRYPT_USERDATA 所有微软CSP
都忽略此参数。所有其他CSP
都不能忽略此参数,如果置此参数,CSP
提示用户直接传入数据。
函数功能:
此函数把一段数据加入到指定的哈希对象中去。
e.g.
CryptHashData(hCryptHash, (BYTE*)hash_data, data_len,
0);
好吧,为了调用这个函数,我们需要一个HCRYPTHASH类型的哈希句柄,那么这个句柄怎么来呢……
Step2:
不难发现,生成哈希句柄的函数为CryptCreateHash,同样看看他的参数说明:
BOOL WINAPI CryptCreateHash(HCRYPTPROV hProv,
ALG_ID Algid, //想使用的哈希算法
HCRYPTKEY hKey,
DWORD dwFlags,
HCRYPTHASH *phHash
//获得的哈希句柄
);
参数:
hProv
[in]CSP 句柄
Algid
[in]哈希算法的标示符。
hKey
[in]如果哈希算法是密钥哈希,如HMAC、MAC
算法,就用此密钥句柄传递密钥。对于非密钥算法,此参数为NULL。
dwFlags
[in]保留。必须为0。
phHash
[out]哈希对象的句柄。
功能:
此函数初始化哈希数据流。它创建并返回了一个CSP
哈希对象的句柄。此句柄由CryptHashData
和CryptHashSessionKey
来调用。
e.g.
CryptCreateHash(hCryptProv,CALG_MD5,0,0,&hCryptHash)
这里可以通过Algid参数设置你需要的哈希算法对应的预编译常量,就可以获取哈希句柄。但是……获取哈希句柄需要传入一个CSP句柄(密钥容器),所以需要一个获取该句柄的函数。
Step3:
直接利用函数CryptAcquireContext就可以获取HCRYPTPROV类型的密钥容器句柄了,该函数参数声明如下:
BOOL WINAPI CryptAcquireContext(HCRYPTPROV
*phProv,//返回的CSP句柄
LPCTSTR pszContainer,
LPCTSTR pszProvider,
DWORD dwProvType,
DWORD dwFlags
);
参数:
phProv
[out] CSP 句柄指针
pszContainer
[in] 密钥容器名称, 指向密钥容器的字符串指针。如果dwFlags
为
CRYPT_VERIFYCONTEXT,pszContainer
必须为NULL。
pszProvider
[in]指向CSP
名称的字符串指针。如果为NULL,就使用却省的CSP。
dwProvType
[in]CSP 类型。
dwFlags
[in]标志。
CRYPT_VERIFYCONTEXT
此选项指出应用程序不需要使用公钥/私钥对,如程序只执行哈希和对称加密。
只有程序需要创建签名和解密消息时才需要访问私钥。
CRYPT_NEWKEYSET
使用指定的密钥容器名称创建一个新的密钥容器。如果pszContainer
为NULL,
密钥容器就使用却省的名称创建。
CRYPT_MACHINE_KEYSET
由此标志创建的密钥容器只能由创建者本人或有系统管理员身份的人使用。
CRYPT_DELETEKEYSET
删除由pszContainer
指定的密钥容器。如果pszContainer
为NULL,却省名称
的容器就会被删除。此容器里的所有密钥对也会被删除。
CRYPT_SLIENT
应用程序要求CSP
不显示任何用户界面。
功能:
这个函数是用来取得指定CSP
密钥容器句柄,以后的任何加密操作就是针对此CSP
句
柄而言。函数首先查找由dwProvType
和pszProvider
指定的CSP,如果找到了CSP,函数就查找由此CSP
指定的密钥容器。由适当的dwFlags
标志,这个函数就可以创建和销毁密钥容器,如果不要求访问私钥的话,也可以提供对CSP
临时密钥容器的访问。
e.g.
CryptAcquireContext(&hCryptProv, NULL, MS_DEF_PROV,
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)
这样……如果从第三步出发到第一步,我们已经成功地把哈希结果添加到了哈希句柄中,考虑到CryptHashData并没有直接返回哈希结果,我们需要使用CryptGetHashParam取出哈希句柄中的哈希结果。
Step4:
CryptGetHashParam函数可以通过哈希句柄,获得刚刚哈希出来的结果,其声明如下:
BOOL WINAPI CryptGetHashParam(HCRYPTHASH
hHash,//刚刚的哈希句柄
DWORD dwParam,//你需要的结果类型
BYTE *pbData,//存储结果的缓冲区指针
DWORD *pdwDataLen,
//最大缓冲区长度
DWORD dwFlags
);
参数:
hHash
[in]哈希对象的句柄
dwParam
[in]查询类型。可以是下列:
HP_ALGID 哈希算法
HP_HASHSIZE 哈希值长度
HP_HASHVAL 哈希值,由hHash
指定的哈希值或者消息哈希
功能:
此函数得到指定哈希对象的数据。
e.g.
CryptGetHashParam(hCryptHash, HP_HASHVAL, (BYTE*)hash_data,
&hash_len, 0)
这里要强调几个事情:
① pdwDataLen参数是输入输出的,特别要注意它的初始值,一旦小于哈希值长度,将返回莫名其妙的结果。用GetLastError可以获取错误代码234,即ERROR_MORE_DATA。
与最终结果对比上图:
②对以上四个函数最好都进行返回值判断,如果返回值为假,表示执行出了问题,这时可以用GetLastError获取错误信息,查找Error
Code搞清楚错误原因,不然会有莫名其妙的结果……
③由于获取的哈希结果是BYTE数组,以MD5
32位哈希为例,如果直接以字符串形式或者%x输出都会有问题。因为是4bit对应一个Hex码,所以要进行一下转换,否则:
……直接输出字符串
……直接十六进制输出
好吧,那就处理一下吧……
Step5:
分别取一个BYTE的前四位和后四位,然后放到int里面,再用16进制输出即可,这里使用了一个itoa做int到hex字串的转换。
代码如下:
int hash_bit =
hash_data[i];
int first = (hash_bit & 0xf0) >> 4; //取前四位
int second = hash_bit & 0x0f;
//取后四位
char tmp[10];
itoa(first, tmp, 16);
cout << tmp;
itoa(second, tmp, 16);
cout << tmp;
这样就可以获得正确的32位结果了,对应16位结果取中间16位即可,如”admin”:21232f297a57a5a743894a0e4a801fc3,16位结果为7a57a5a743894a0e。
最后试试实现一些其他的哈希算法吧:
P.S.最后不要忘记使用CryptDestroyHash和CryptReleaseContext释放哈希句柄和CSP句柄。
核心代码:
bool
CCryptoApiHashDlg::GetHash(int hash_type, CString& hash_result,
static CString hash_message)
{
HCRYPTPROV hCryptProv;
HCRYPTHASH hCryptHash;
if (!CryptAcquireContext(&hCryptProv, NULL, MS_DEF_PROV,
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
int e = GetLastError();
CString str;
str.Format("创建CSP容器出错!错误代码为:%d!",
e);
MessageBox(str, "出错啦!",
MB_OK | MB_ICONERROR);
return false;
}
if (!CryptCreateHash(hCryptProv, hash_type, 0, 0,
&hCryptHash))
{
int e = GetLastError();
CString str;
str.Format("创建哈希句柄出错!错误代码为:%d!",
e);
MessageBox(str, "出错啦!",
MB_OK | MB_ICONERROR);
return false;
}
if (!CryptHashData(hCryptHash, (BYTE*)hash_message.GetBuffer(),
hash_message.GetLength(), 0))
{
int e = GetLastError();
CString str;
str.Format("计算哈希值出错!错误代码为:%d!",
e);
MessageBox(str, "出错啦!",
MB_OK | MB_ICONERROR);
return false;
}
char hash_data[512];
DWORD hash_len = 512;
if (!CryptGetHashParam(hCryptHash, HP_HASHVAL, (BYTE*)hash_data,
&hash_len, 0))
{
int e = GetLastError();
CString str;
str.Format("获取哈希值出错!错误代码为:%d!",
e);
MessageBox(str, "出错啦!",
MB_OK | MB_ICONERROR);
return false;
}
char hash_hex[512];
for (int i=0; i<=hash_len-1; i++)
{
int hash_bit = hash_data[i];
int first = (hash_bit & 0xf0) >> 4;
int second = hash_bit & 0x0f;
char tmp[2];
itoa(first, tmp, 16);
hash_hex[i*2] = tmp[0];
itoa(second, tmp, 16);
hash_hex[i*2+1] = tmp[0];
}
hash_hex[hash_len*2] = '\0';
hash_result.Format("%s", hash_hex);
CryptDestroyHash(hCryptHash);
CryptReleaseContext(hCryptProv, NULL);
return true;
}