EternalWindows
DPAPI / 新しいDPAPI

WindowsVistaからは、DPAPIにCryptProtectMemoryという新しい関数が追加されました。 この関数もCryptProtectDataと同じくデータを暗号化する関数ですが、 その暗号化データの扱い方は互いに異なります。 CryptProtectDataが暗号化データをファイルなどの外部に記憶することを想定にしているのに対し、 CryptProtectMemoryは暗号化データを自プロセス内で維持したり、 他のプロセスに送信したりすることを想定しています。 また、CryptProtectMemoryによって作成された暗号化データは、 データを複合化できる期間というものが存在します。 この仕様により、仮に暗号化データが漏洩したとしても、 ファイルなどに永続的に維持されるような問題を意識する必要はありません。 CryptProtectMemoryは、次のように定義されています。

BOOL CryptProtectMemory(
  LPVOID pData,
  DWORD cbData,
  DWORD dwFlags
);

pDataは、暗号化したいデータを指定します。 cbDataは、pbDataのサイズを指定します。 この値は、CRYPTPROTECTMEMORY_BLOCK_SIZE定数の倍数でなければなりません。 dwFlagsは、次に示す定数のいずれかを指定します。

定数 説明
CRYPTPROTECTMEMORY_SAME_PROCESS 暗号化を行ったプロセスだけがデータを復号化できる。 プロセスが終了するとデ−タを複合化することはできない。
CRYPTPROTECTMEMORY_CROSS_PROCESS 暗号化を行ったプロセスだけでなく、別のプロセスもデータを複合化できる。 システムをシャットダウンするとデ−タを複合化することはできない。
CRYPTPROTECTMEMORY_SAME_LOGON 暗号化を行ったプロセスだけでなく、別のプロセスもデータを複合化できる。 ただし、そのプロセスは暗号化を行ったプロセスと同じ ログオンセッションで動作している必要がある。 システムをシャットダウンするとデ−タを複合化することはできない。

上記のようにCryptProtectMemoryには、プロセスの終了時やシステムのシャットダウン時などの 期間が存在しますが、この期間を超えるとCryptProtectMemoryを呼び出したアプリケーションも、 データを複合化できなくなることに注意してください。 たとえば、CRYPTPROTECTMEMORY_SAME_PROCESSを指定して作成した暗号化データをファイルに保存した場合、 一度プロセスが終了すれば、そのファイル内のデータは誰も複合化できなくなります。 ただし、これは不都合な仕様ということではなく、CryptProtectMemoryで暗号化したデータを 永続的に維持しようとした設計に問題があるのです。 このような場合は、CryptProtectDataでデータを暗号化し、それを保存するようにします。

CryptProtectMemoryで暗号化されたデータは、CryptUnprotectMemoryで複合化することができます。

BOOL CryptUnprotectMemory(
  LPVOID pData,
  DWORD cbData,
  DWORD dwFlags
);

pDataは、複合化したいデータを指定します。 cbDataは、pDataのサイズを指定します。 dwFlagsは、CryptProtectMemoryに指定したdwFlagsと同じ値を指定します。

今回のプログラムは、CryptProtectMemoryを呼び出す例を示します。

#include <windows.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	LPBYTE lpEncryptData;
	DWORD  dwEncryptDataSize;
	DWORD  dwMod;
	LPBYTE lpData = (LPBYTE)TEXT("sample-data");
	DWORD  dwSize = sizeof(TEXT("sample-data"));

	dwMod = dwSize % CRYPTPROTECTMEMORY_BLOCK_SIZE;
	if (dwMod == 0)
		dwEncryptDataSize = dwSize;
	else
		dwEncryptDataSize = dwSize + (CRYPTPROTECTMEMORY_BLOCK_SIZE - dwMod);
	
	lpEncryptData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptDataSize);
	CopyMemory(lpEncryptData, lpData, dwSize);

	if (!CryptProtectMemory(lpEncryptData, dwEncryptDataSize, CRYPTPROTECTMEMORY_SAME_PROCESS)) {
		MessageBox(NULL, TEXT("データの暗号化に失敗しました。"), NULL, MB_ICONWARNING);
		SecureZeroMemory(lpEncryptData, dwEncryptDataSize);
		HeapFree(GetProcessHeap(), 0, lpEncryptData);
		return 0;
	}

	MessageBox(NULL, TEXT("データを暗号化しました。"), TEXT("OK"), MB_OK);

	if (CryptUnprotectMemory(lpEncryptData, dwEncryptDataSize, CRYPTPROTECTMEMORY_SAME_PROCESS))
		MessageBox(NULL, TEXT("データを複合化しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("データの複合化に失敗しました。"), NULL, MB_ICONWARNING);

	MessageBox(NULL, (LPTSTR)lpEncryptData, TEXT("OK"), MB_OK);

	SecureZeroMemory(lpEncryptData, dwEncryptDataSize);
	HeapFree(GetProcessHeap(), 0, lpEncryptData);

	return 0;
}

暗号化したいデータをlpDataに、そのサイズをdwSizeに指定しています。 今回のプログラムでは、データをハードコードしていますが、 実際の開発ではユーザーが入力したパスワードなどが対象となるでしょう。 CryptProtectMemoryを呼び出すためには、まず暗号化後のデータのサイズを計算する必要があります。

dwMod = dwSize % CRYPTPROTECTMEMORY_BLOCK_SIZE;
if (dwMod == 0)
	dwEncryptDataSize = dwSize;
else
	dwEncryptDataSize = dwSize + (CRYPTPROTECTMEMORY_BLOCK_SIZE - dwMod);

暗号化データのサイズは、CRYPTPROTECTMEMORY_BLOCK_SIZEの倍数になっている必要があります。 dwSizeでこれを除算した場合、余りが0であれば倍数が正しいことが分かりますが、 そうでない場合は適切に調整する必要があります。 どれだけの値をdwSizeに加算すれば、CRYPTPROTECTMEMORY_BLOCK_SIZEの倍数になるかは、 CRYPTPROTECTMEMORY_BLOCK_SIZE - dwModで算出できます。

dwEncryptDataSizeに暗号化後のデータサイズが格納されたため、 暗号化データを受け取るためのメモリを確保することができます。

lpEncryptData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptDataSize);
CopyMemory(lpEncryptData, lpData, dwSize);

確保したメモリに暗号化したいデータをコピーする点が重要です。 このデータがCryptProtectMemoryによって暗号化され、 そのサイズはdwEncryptDataSizeで表すことができます。 関数が失敗した場合やデータが不要になった場合は、 SecureZeroMemoryで使用したメモリをクリアしてから開放します。

今回のプログラムでは、暗号化したデータを直ぐにCryptUnprotectMemoryで複合化していますが、 実際の開発ではユーザーがデータを要求した際に、CryptUnprotectMemoryを呼び出すことになると思われます。 また、CryptProtectMemoryにCRYPTPROTECTMEMORY_CROSS_PROCESSを指定した場合は、 他のアプリケーションがデータを受信した際に、CryptUnprotectMemoryを呼び出すことになるでしょう。 重要なデータがプロセスのアドレス空間や、他のアプリケーションに送信する場合に 保護されることを望むのは当然ですから、CryptProtectMemoryは積極的に使用するべきといえます。


戻る