使用 CryptAPI 进行数据加密
CryptAPI 简述
CryptAPI 是微软提供的数据加密套件,使用该套件可以实现对称加密、非对称加密、哈希算法及数字签名等。
CSP 基础
CSP(Cryptographic Service Providers),加密服务提供者,是独立的加密模块。CSP 内部定义了特定的算法(例如 DES、AES 等),实际上数据的加密与解密便是通过 CSP 完成的。
CSP 中包含三个重要的概念:Key Container,Cryptographic Provider Name 和 Cryptographic Provider Type。
- Key Container:用于存放加密密钥的区域,使用字符串进行标识,每个 CSP 可以包含多个密钥容器
- Provider Name:Service Provider 的名称,用于区分不同的加密实现模块
- Provider Type:用于标识 Serivce Provider 提供的功能和加密算法类型
CSP 模块一般以 DLL 的形式存在,并向外暴露统一的函数接口,CryptAPI 通过载入不同的 CSP 模块,调用相同规范的函数,便可以实现各种各样的加密运算。
加密流程
首先,必须通过 CryptAcquireContext 函数来初始化一个 CSP 模块,CryptAcquireContext 函数定义如下:
BOOL WINAPI CryptAcquireContext(
HCRYPTPROV *phProv,
LPCTSTR pszContainer,
LPCTSTR pszProvider,
DWORD dwProvType,
DWORD dwFlags
);
pszContainer 用于指定 CSP 存放密钥的容器名称,可以自定义,也可以传入 NULL 使用默认的密钥容器。pszProvider 和 dwProvType 分别对应 CSP 的名称和类型。例如 MS_DEF_PROV 模块提供了公钥加密算法,其算法类型为 RSA。最后的 dwFlags 可以设置为 0。
函数调用成功后,CSP 模块的句柄会保存到 hProv 参数中。
接着,将用户明文密码转换为 Hash 进行存储,这里的 Hash 算法只用于加密密码,而非用户数据。使用 CryptCreateHash 获取一个 Hash 算法对象:
BOOL WINAPI CryptCreateHash(
HCRYPTPROV hProv,
ALG_ID Algid,
HCRYPTKEY hKey,
DWORD dwFlags,
HCRYPTHASH *phHash
);
hProv 为 CryptAcquireContext 获取的 CSP 句柄,Algid 为 Hash 算法类型,hKey 和 dwFlags 默认为 0,成功后 Hash 对象句柄存放于 hHash 中。
获取 Hash 算法对象句柄后,使用 CryptHashData 将密码更新到 Hash 对象中:
BOOL WINAPI CryptHashData(
HCRYPTHASH hHash,
BYTE *pbData,
DWORD dwDataLen,
DWORD dwFlags
);
得到密文 Hash 后,使用函数 CryptDeriveKey 创建一个密钥对象,该密钥存放于 CSP 的密钥容器中,加密与解密使用该密钥来进行:
BOOL WINAPI CryptDeriveKey(
HCRYPTPROV hProv,
ALG_ID Algid,
HCRYPTHASH hBaseData,
DWORD dwFlags,
HCRYPTKEY *phKey
);
Algid 指定一个加密算法,hBaseData 是加密的 Hash 对象,dwFlags 可以设置为 CRYPT_EXPORTABLE,这样后期可以把密钥导出,当然使用原明文密码也是可以解密的。
成功后 hKey 便最终的密钥对象。
跋山涉水获取到密钥对象后,就可以使用 CryptEncrypt 和 CryptDecrypt 对数据进行加密,CryptEncrypt 定义如下:
BOOL WINAPI CryptEncrypt(
HCRYPTKEY hKey,
HCRYPTHASH hHash,
BOOL Final,
DWORD dwFlags,
BYTE *pbData,
DWORD *pdwDataLen,
DWORD dwBufLen
);
如果要计算数据的签名值,可以传入一个 hHash 对象(同密码 Hash 不是同个对象),否则为 NULL。
Final 指定是否为最后一个数据块,例如分块从文件中读取内容,最后的分块必须将该标志设置为 TRUE。
pbData 和 pdwDataLen 是原始数据内容和大小。
dwFlags 为系统保留,置零。
加密后的数据会覆盖到明文数据区域,即 pbData 指定的内存空间,密文大小到 dwDataLen 参数中,所以必须保证原始内容的内存区域可写,并将其大小指定到 dwBufLen 参数中。
当程序结束前,需要释放各个资源句柄。CryptDestroyHash 和 CryptDestroyKey 用于销毁 Hash 对象和密钥对象,最后,调用 CryptReleaseContext 释放整个 CSP 模块。
Talk is Cheap, Show Me The Code
下面代码通过 CryptAPI ,使用 AES-256 对称加密算法对用户输入的文本进行加密,并以 16 进制的结果进行输出:
#include <Windows.h>
#include <wincrypt.h>
#include <stdio.h>
int main(int argc, char** argv)
{
int nRet = -1, i;
HCRYPTPROV hProv;
HCRYPTKEY hKey;
HCRYPTHASH hHash;
DWORD dwDataLen = 0;
LPBYTE lpBuffer = NULL;
LPBYTE lpData = NULL, lpPassword = NULL;
if (argc < 3) {
printf("Usage: EncryptData <password> <plain-text>\n");
return nRet;
}
lpData = argv[2];
lpPassword = argv[1];
// 使用 MS_ENH_RSA_AES_PROV CSP 模块,其支持 AES 加密
if (! CryptAcquireContext(&hProv, NULL,
MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0)) {
printf("A CSP handle could not be acquired. Error code: %d\n", GetLastError());
return nRet;
}
// 获取 MD5 哈希算法对象
if (! CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
printf("Cannot create the hash handle. Error code: %d\n", GetLastError());
goto release;
}
// 使用 MD5 对用户密码进行加密
if (!CryptHashData(hHash, lpPassword, strlen(lpPassword) * sizeof(char), 0)) {
printf("The password cloud not be hashed. Error code: %d\n", GetLastError());
goto release;
}
// 通过 Hash 密码生成密钥对象,加密类型为 AES-256-CBC
if (! CryptDeriveKey(hProv, CALG_AES_256, hHash, CRYPT_EXPORTABLE, &hKey)) {
printf("Cannot create the key handle. Error code: %d\n", GetLastError());
goto release;
}
dwDataLen = strlen(lpData) * sizeof(char);
lpBuffer = (LPBYTE) malloc(dwDataLen * 2);
memcpy(lpBuffer, lpData, dwDataLen);
// 加密数据
if (! CryptEncrypt(hKey, 0, TRUE, 0, lpBuffer, &dwDataLen, dwDataLen * 2)) {
printf("Failed to encrypt the special data. Error code: %d\n", GetLastError());
goto release;
}
// 输出 16 进制格式密文
for (i = 0; i < dwDataLen; i++) {
printf("%02X", lpBuffer[i]);
}
printf("\n");
free(lpBuffer);
release:
if (hHash != NULL)
{
CryptDestroyHash(hHash);
}
if (hKey != NULL)
{
CryptDestroyKey(hKey);
}
if (hProv != NULL)
{
CryptReleaseContext(hProv, 0);
}
return 0;
}
执行结果:
参考资料
The Cryptography API, or How to Keep a SecretThe Cryptography API, or How to Keep a Secret