用DPAPI保护你的数据
我们已经知道,用Win32API函数EncryptFile和DecryptFile可以方便的加解密文件。但是,这两个函数与系统帐户联系过于紧密,能否解密完全看当前帐户是否有更高的权限,用户的数据实际上并没有得到真正的保护。下面将介绍一种更为灵活的数据保护方法。
从Win2000开始,操作系统开始提供一个名为Data Protection API (DPAPI)的数据保护接口。该接口一共有两个函数,他们提供了系统级的数据保护服务。这两个函数存在于Crypt32.dll库中,是CryptAPI的一部分,但使用起来要比其他的CryptAPI函数简单得多。
DPAPI可以实现基于口令的数据加密和解密。也就是说我们提供一个口令用于加密,而其他人只有知道这个口令才能解密。实际上,DPAPI上在后台为我们完成了相当复杂的加密解密操作,包括密钥的产生、存储、使用等。本文只想介绍一下如何使用DPAPI,这里就不讨论它的内部机制了。
DPAPI加密函数
BOOL WINAPI CryptProtectData (
[IN] DATA_BLOB *pDataIn, //输入数据,明文
[IN] LPCWSTR szDataDescr, //描述信息
[IN] DATA_BLOB *pOptionalEntropy, //额外的保护信息
[IN] PVOID pvReserved, //保留参数,必须为NULL
[IN] CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct, //提示对话框结构
[IN] DWORD dwFlags, //标志位
[OUT] DATA_BLOB *pDataOut //输出数据,密文
);
输入数据参数pDataIn是一个DATA_BLOB结构,该结构的定义如下:
typedef struct _CRYPTOAPI_BLOB {
DWORD cbData; //数据的长度
BYTE* pbData; //指向数据的指针
}DATA_BLOB;
参数szDataDescr是一个字串,可以是关于加密的描述信息或其他的任何信息,但不能为NULL,该信息将以明文的方式存在于最终输出的密文数据中。
参数pOptionalEntropy用于额外的密码保护,本文后面将有详细的介绍。
参数pPromptStruct用于指定一个安全提示对话框,在加密解密操作时,将弹出该对话框提示用户正在进行安全操作。如果该参数为NULL,则不会弹出对话框。这里就不在列出CRYPTPROTECT_PROMPTSTRUCT结构的详细定义,你可以查阅MSDN的相关内容。后面的程序将把该参数设置为NULL;
参数dwFlags是关于加密的一些选项标志,一般设置为0就可以了,详细的说明请参阅MSDN的相关内容;
参数pDataOut是输出数据,同样是一个DATA_BLOB结构,但应该注意的是pDataOut->pbData所指向的内存是由系统分配的,在使用完输出数据后应该用LocalFree函数释放该内存。
DPAPI解密函数
BOOL WINAPI CryptUnprotectData (
[IN] DATA_BLOB *pDataIn, //输入数据,密文
[OUT] LPCWSTR *ppszDataDescr, //输出描述信息
[IN] DATA_BLOB *pOptionalEntropy, //额外的保护信息
[IN] PVOID pvReserved, //保留参数,必须为NULL
[IN] CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct, //提示对话框结构
[IN] DWORD dwFlags, //标志位
[OUT] DATA_BLOB *pDataOut //输出数据,明文
);
解密函数的参数与加密函数的参数基本类似,唯一需要指出的是参数ppszDataDescr在这里应该被指定为一个指针变量,在解密完成后,该变量将指向加密时指定的描述信息,最后应该使用LocalFree函数释放该字串。如果你不想得到描述信息,可以将该参数设置为NULL。
DPAPI和系统帐户联系仍然十分紧密,他会自动使用当前用户的系统登录口令作为加解密的口令,这样一来同一台机器的所有程序都可以解密其他程序加密的数据。为了防止这一点,函数提供了pOptionalEntropy参数,使我们有机会使用自己的口令。如果提供了pOptionalEntropy参数,DPAPI将使用当前用户系统登录口令和我们提供的额外保护口令的组合进行加解密操作。如果你不想使用额外口令保护,则可设置该参数为NULL。
因为DPAPI使用用户帐户联系的,所以一台机器上加密的数据,一般不能在另一台机器上解密(特殊情况请参阅MSDN相关内容)。
下面的程序利用DPAPI实现了文件的加密和解密,其中Password是可选的,但如果加密时使用了Password,那么解密时就必须提供相同的Password.。
/********************************************************
*DPAPI.EXE
*Encrypt and Decrypt file
*Wrote by WABC on 2002-5-20
********************************************************/
#include <windows.h>
#include <wincrypt.h>
DWORD MyEncryptFile(LPCSTR FileName,LPCSTR OutputFileName,LPCSTR Password);
DWORD MyDecryptFile(LPCSTR FileName,LPCSTR OutputFileName,LPCSTR Password);
void PrintUsage();
int main(int argc,char *argv[])
{
//Command Line: dpapi <e/d> <filename> <output filename> <password>
if(argc<4 || argc>5){
PrintUsage();
return 0;
}
char *FileName=argv[2];
char *OutputFileName=argv[3];
char *Password=NULL;
if(argc==5)Password=argv[4];
DWORD rc=0;
if(argv[1][0]=='e'){ //Encrypt
rc=MyEncryptFile(FileName,OutputFileName,Password);
if(rc!=0){
printf("Can't encrypt the file '%s',error code:%d.\n",FileName,rc);
return 0;
}
printf("encrypt successfully!\n");
}else{ //Decrypt
rc=MyDecryptFile(FileName,OutputFileName,Password);
if(rc!=0){
printf("Can't decrypt the file '%s',error code:%d.\n",FileName,rc);
return 0;
}
printf("decrypt successfully!\n");
}
return 0;
}
//Encrypt function
DWORD MyEncryptFile(LPCSTR FileName,LPCSTR OutputFileName,LPCSTR Password)
{
HANDLE hFile=NULL;
DWORD FileLen=0;
BYTE *FileData=NULL;
DATA_BLOB DataIn;
DATA_BLOB DataOut;
DATA_BLOB DataPwd;
DATA_BLOB *pDataPwd=NULL;
ZeroMemory(&DataIn,sizeof(DATA_BLOB));
ZeroMemory(&DataOut,sizeof(DATA_BLOB));
ZeroMemory(&DataPwd,sizeof(DATA_BLOB));
if(Password!=NULL){
DataPwd.cbData=strlen(Password);
DataPwd.pbData=(BYTE*)Password;
pDataPwd=&DataPwd;
}
try{
hFile=CreateFile(FileName,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)throw "";
FileLen=GetFileSize(hFile,NULL);
FileData=new BYTE[FileLen];
if(FileData==NULL)throw "";
ReadFile(hFile,FileData,FileLen,&FileLen,NULL);
CloseHandle(hFile);
hFile=NULL;
DataIn.pbData=FileData;
DataIn.cbData=FileLen;
if(!CryptProtectData(
&DataIn,
L"This is the description string.", // A description sting.
pDataPwd, // Optional entropy not used.
NULL, // Reserved.
NULL, // Pass a PromptStruct.
0,
&DataOut))
{
throw "";
}
delete[] FileData;
FileData=NULL;
hFile=CreateFile(OutputFileName,GENERIC_WRITE,0,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)throw "";
WriteFile(hFile,DataOut.pbData,DataOut.cbData,&FileLen,NULL);
CloseHandle(hFile);
hFile=NULL;
LocalFree(DataOut.pbData);
ZeroMemory(&DataOut,sizeof(DataOut));
}catch(...){
if(hFile)CloseHandle(hFile);
if(FileData)delete[] FileData;
if(DataOut.pbData)LocalFree(DataOut.pbData);
return GetLastError();
}
return 0;
}
//Decrypt function
DWORD MyDecryptFile(LPCSTR FileName,LPCSTR OutputFileName,LPCSTR Password)
{
HANDLE hFile=NULL;
DWORD FileLen=0;
BYTE *FileData=NULL;
DATA_BLOB DataIn;
DATA_BLOB DataOut;
DATA_BLOB DataPwd;
DATA_BLOB *pDataPwd=NULL;
ZeroMemory(&DataIn,sizeof(DATA_BLOB));
ZeroMemory(&DataOut,sizeof(DATA_BLOB));
ZeroMemory(&DataPwd,sizeof(DATA_BLOB));
if(Password!=NULL){
DataPwd.cbData=strlen(Password);
DataPwd.pbData=(BYTE*)Password;
pDataPwd=&DataPwd;
}
try{
hFile=CreateFile(FileName,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)throw "";
FileLen=GetFileSize(hFile,NULL);
FileData=new BYTE[FileLen];
if(FileData==NULL)throw "";
ReadFile(hFile,FileData,FileLen,&FileLen,NULL);
CloseHandle(hFile);
hFile=NULL;
DataIn.pbData=FileData;
DataIn.cbData=FileLen;
if(!CryptUnprotectData(
&DataIn,
NULL,
pDataPwd, // Optional entropy
NULL, // Reserved
NULL, // Optional PromptStruct
0,
&DataOut))
{
throw "";
}
delete[] FileData;
FileData=NULL;
hFile=CreateFile(OutputFileName,GENERIC_WRITE,0,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)throw "";
WriteFile(hFile,DataOut.pbData,DataOut.cbData,&FileLen,NULL);
CloseHandle(hFile);
hFile=NULL;
LocalFree(DataOut.pbData);
ZeroMemory(&DataOut,sizeof(DataOut));
}catch(...){
if(hFile)CloseHandle(hFile);
if(FileData)delete[] FileData;
if(DataOut.pbData)LocalFree(DataOut.pbData);
return GetLastError();
}
return 0;
}
void PrintUsage()
{
printf("Usage: dpapi <e/d> <filename> <output filename> [password]\n");
printf(" e encrypt file\n");
printf(" d decrypt file\n");
}
要想编译上面的程序,你必须首先下载一个PlatfromSDK(因为VC附带的Crypt32.lib导出库中没有DPAPI的函数说明,但操作系统的Crypt32.dll中确实存在DPAPI函数),然后把PlatformSDK中的Crypt32.lib加入到你的VC工程中,最后在VC工程中的stdafx.h中加入宏定义#define _WIN32_WINNT 0x500。
使用DPAPI应该注意以下几点:
1、加密和解密操作必须在同一台计算机上进行;
2、如果加密时提供了额外的口令保护,那么解密时必须提供相同的口令;
3、在DPAPI函数之后,一定要记着用LocalFree函数释放系统分配的内存;
4、DPAPI没有存储数据的机制,应用程序必须负责存储加密或解密后的数据。