EternalWindows
DPAPI / DPAPIによる暗号化

DPAPIによる暗号化は、CryptProtectDataで行われることになります。

BOOL WINAPI CryptProtectData(
  DATA_BLOB *pDataIn,
  LPCWSTR szDataDescr,
  DATA_BLOB *pOptionalEntropy,
  PVOID pvReserved,
  CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,
  DWORD dwFlags,
  DATA_BLOB *pDataOut
);

pDataInは、暗号化したいデータを格納したDATA_BLOB構造体のアドレスを指定します。 szDataDescrは、作成される暗号化データに含みたい文字列を指定します。 pOptionalEntropyは、アプリケーションだけが知り得るエントロピーを指定します。 WindowsXPからは、不要な場合にNULLを指定することができます。 pvReservedは、予約されているためNULLを指定します。 pPromptStructは、パスワードを入力するためのダイアログ情報を表す構造体を指定します。 不要な場合は、NULLを指定します。 dwFlagsは、定義されている専用の定数を指定します。 pDataOutは、暗合化されたデータを受け取るDATA_BLOB構造体のアドレスを指定します。

通常、CryptProtectDataで暗号化されたデータは、 暗号化時と同じユーザーで実行されているアプリケーションならば、 データを複合化することができます。 これを防ぐには、CryptProtectDataにエントロピーかパスワードのいずれか、 または両方を指定するようにします。 これらはどちらも外部に漏洩してはならない秘密の値ですが、 エントロピーがアプリケーション内で自ら取得しなければならない値であるのに対し、 パスワードはダイアログ経由でユーザーが入力するという点が異なります。 パスワードの入力を促す場合は、pPromptStructにCRYPTPROTECT_PROMPTSTRUCT構造体を指定します。

typedef struct _CRYPTPROTECT_PROMPTSTRUCT{
  DWORD      cbSize;
  DWORD      dwPromptFlags;
  HWND       hwndApp;
  LPCWSTR    szPrompt;
} CRYPTPROTECT_PROMPTSTRUCT, *PCRYPTPROTECT_PROMPTSTRUCT;

cbSizeは、この構造体のサイズをバイト単位で指定します。 dwPromptFlagsは、定義されている定数を指定します。 hwndAppは、ダイアログの親ウインドウとするハンドルを指定します。 szPromptは、ダイアログのタイトルとする文字列を指定します。

CryptProtectDataのdwFlagsには基本的に0を指定しますが、 CRYPTPROTECT_LOCAL_MACHINEを指定することもできます。 この定数を指定した場合、暗号化時に現在のユーザーのユーザープロファイルではなく、 ローカルコンピュータのプロファイルが参照されます。 これは全てのユーザーが参照できるプロファイルであるため、 作成されたデータはローカルコンピュータの全ユーザーが複合化できることになり、 あまり推奨されるものではありません。

CryptProtectDataで暗号化したデータは、CryptUnprotectDataで複合化することになります。

BOOL WINAPI CryptUnprotectData(
  DATA_BLOB *pDataIn,
  LPWSTR *ppszDataDescr,
  DATA_BLOB *pOptionalEntropy,
  PVOID pvReserved,
  CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,
  DWORD dwFlags,
  DATA_BLOB *pDataOut
);

pDataInは、複合化したいデータを指定します。 ppszDataDescrは、暗号化データに含まれている文字列を受け取る変数のアドレスを指定します。 不要な場合は、NULLを指定することができます。 pOptionalEntropyは、エントロピーを格納したDATA_BLOB構造体のアドレスを指定します。 暗号化の際にエントロピーを指定した場合は、この引数を指定します。 pvReservedは、予約されているためNULLを指定します。 pPromptStructは、CRYPTPROTECT_PROMPTSTRUCT構造体のアドレスを指定します。 dwFlagsは、定義されている定数を指定します。 pDataOutは、複合化されたデータを受け取るDATA_BLOB構造体のアドレスを指定します。

今回のプログラムは、CryptProtectDataで暗号化したデータをファイルに保存します。 また、暗号化されたデータをファイルから取得し、複合化するコードも含まれます。

#include <windows.h>

#pragma comment (lib, "crypt32.lib")

BOOL EncryptData(LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize, LPBYTE lpEntropy, DWORD dwEntropySize);
BOOL DecryptData(LPTSTR lpszFileName, LPBYTE lpEntropy, DWORD dwEntropySize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR szFileName[] = TEXT("sample.dat");
	TCHAR szData[] = TEXT("sample-data");
	TCHAR szEntropy[] = TEXT("entropy");
	BOOL  bEncrypt = TRUE;

	if (bEncrypt)
		EncryptData(szFileName, (LPBYTE)szData, sizeof(szData), (LPBYTE)szEntropy, sizeof(szEntropy));
	else
		DecryptData(szFileName, (LPBYTE)szEntropy, sizeof(szEntropy));

	return 0;
}

BOOL EncryptData(LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize, LPBYTE lpEntropy, DWORD dwEntropySize)
{
	DATA_BLOB                 blobIn;
	DATA_BLOB                 blobEntropy;
	DATA_BLOB                 blobOut;
	CRYPTPROTECT_PROMPTSTRUCT promptStruct;
	HANDLE                    hFile;
	DWORD                     dwWriteByte;

	blobIn.cbData = dwDataSize;
	blobIn.pbData = lpData;

	blobEntropy.cbData = dwEntropySize;
	blobEntropy.pbData = lpEntropy;

	promptStruct.cbSize        = sizeof(CRYPTPROTECT_PROMPTSTRUCT);
	promptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;
	promptStruct.hwndApp       = NULL;
	promptStruct.szPrompt      = L"データの暗号化";

	if (!CryptProtectData(&blobIn, NULL, &blobEntropy, NULL, &promptStruct, 0, &blobOut)) {
		MessageBox(NULL, TEXT("データの暗号化に失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}
	
	MessageBox(NULL, TEXT("データを暗号化しました。"), TEXT("OK"), MB_OK);

	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, blobOut.pbData, blobOut.cbData, &dwWriteByte, NULL);
	CloseHandle(hFile);

	return TRUE;
}

BOOL DecryptData(LPTSTR lpszFileName, LPBYTE lpEntropy, DWORD dwEntropySize)
{
	DATA_BLOB                 blobIn;
	DATA_BLOB                 blobEntropy;
	DATA_BLOB                 blobOut;
	CRYPTPROTECT_PROMPTSTRUCT promptStruct;
	LPBYTE                    lpData;
	DWORD                     dwDataSize;
	HANDLE                    hFile;
	DWORD                     dwReadByte;

	hFile = CreateFile(lpszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;
	
	dwDataSize = GetFileSize(hFile, NULL);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	ReadFile(hFile, lpData, dwDataSize, &dwReadByte, NULL);
	CloseHandle(hFile);

	blobIn.cbData = dwDataSize;
	blobIn.pbData = lpData;

	blobEntropy.cbData = dwEntropySize;
	blobEntropy.pbData = lpEntropy;

	promptStruct.cbSize        = sizeof(CRYPTPROTECT_PROMPTSTRUCT);
	promptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;
	promptStruct.hwndApp       = NULL;
	promptStruct.szPrompt      = L"データの復号化";

	if (!CryptUnprotectData(&blobIn, NULL, &blobEntropy, NULL, &promptStruct, 0, &blobOut)) {
		MessageBox(NULL, TEXT("データの復号化に失敗しました。"), NULL, MB_ICONWARNING);
		return FALSE;
	}
	
	MessageBox(NULL, (LPTSTR)blobOut.pbData, TEXT("OK"), MB_OK);

	SecureZeroMemory(blobOut.pbData, blobOut.cbData);
	LocalFree(blobOut.pbData);
	
	return TRUE;
}

WinMainに定義されているbEncryptがTRUEの場合、暗号化を行うEncryptDataという関数が呼び出され、 FALSEの場合は複合化を行うDecryptData関数が呼び出されます。 szDataは暗号化したいデータであり、暗号化を行うEncryptDataはこれを必要とします。 szEntropyはエントロピーであり、これは暗号化時に指定すると複合化時にも必要になるため、 DecryptDataも要求しています。 EncryptDataの一部を次に示します。

blobIn.cbData = dwDataSize;
blobIn.pbData = lpData;

blobEntropy.cbData = dwEntropySize;
blobEntropy.pbData = lpEntropy;

promptStruct.cbSize        = sizeof(CRYPTPROTECT_PROMPTSTRUCT);
promptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;
promptStruct.hwndApp       = NULL;
promptStruct.szPrompt      = L"データの暗号化";

if (!CryptProtectData(&blobIn, NULL, &blobEntropy, NULL, &promptStruct, 0, &blobOut)) {
	MessageBox(NULL, TEXT("データの暗号化に失敗しました。"), NULL, MB_ICONWARNING);
	return FALSE;
}

暗号化したいデータとエントロピーをDATA_BLOB構造体に設定します。 エントロピーを指定しているため、暗号化されたデータは、 エントロピーを知らない他のアプリケーションから保護されることになります。 これだけでもセキュリティ的な対策は十分といえますが、 今回はCRYPTPROTECT_PROMPTSTRUCT構造体も指定しています。 このため、ダイアログが表示されることになり、 セキュリティレベルに「高」を指定すると、パスワードを入力することになります。

一番上のエディットボックスは、パスワードを入力するためのものではなく、 暗号化データに設定する名前を入力します。 この名前は、実際に暗号化データに格納されることになり、 たとえば、今回のプログラムで作成したファイルの中身を確認すれば、 設定した名前がそのまま保存されていることが分かります。 ちなみに、名前はCryptProtectDataの第2引数でも指定可能であり、 そこで指定した値はエディットボックスのデフォルト値となります。

DecryptDataでは、ファイルからファイルサイズ分のデータを取得し、 それをDATA_BLOB構造体に指定します。 また、暗号化時にエントロピーを指定していたため、 複合化時にも同じエントロピーを指定する必要があります。

blobIn.cbData = dwDataSize;
blobIn.pbData = lpData;

blobEntropy.cbData = dwEntropySize;
blobEntropy.pbData = lpEntropy;

promptStruct.cbSize        = sizeof(CRYPTPROTECT_PROMPTSTRUCT);
promptStruct.dwPromptFlags = CRYPTPROTECT_PROMPT_ON_PROTECT;
promptStruct.hwndApp       = NULL;
promptStruct.szPrompt      = L"データの復号化";

if (!CryptUnprotectData(&blobIn, NULL, &blobEntropy, NULL, &promptStruct, 0, &blobOut)) {
	MessageBox(NULL, TEXT("データの復号化に失敗しました。"), NULL, MB_ICONWARNING);
	return FALSE;
}

CRYPTPROTECT_PROMPTSTRUCT構造体を指定していますが、 パスワードが設定されている場合は、これを指定しなくてもダイアログは表示されます。 しかし、この場合は何故か正しいパスワードを入力しても、関数が失敗するため、 必ず構造体を指定しておく必要があります。 ちなみに、パスワードが設定されていない場合に構造体を指定した場合、 何も表示されることはありません。 サービスのようにUIが表示されることを好まないアプリケーションは、 第6引数にCRYPTPROTECT_UI_FORBIDDENを指定することができます。 この場合、データにパスワードが設定されている場合は、 無条件に関数が失敗することになります。


戻る