ユーザー データの保護と収集
顧客が OEM 登録ページに情報を入力すると、OOBE の完了時に次のファイルが作成されます。
- Userdata.blob。 顧客情報フィールドやチェック ボックスの状態など、登録ページのすべてのユーザー設定可能な要素のすべての値を含む暗号化された XML ファイル。
- SessionKey.blob。 Userdata.blob の暗号化中に生成されます。 暗号化解除プロセスに必要なセッション キーが含まれています。
- Userchoices.xml。 登録ページに含まれるすべてのチェック ボックスのチェック ボックス ラベルと値を含む、暗号化されていない XML ファイル。
Note
顧客が最初の登録ページで Skip
をクリックすると、これらのファイルにデータが書き込まれたり保存されたりすることはなく、チェック ボックスの既定の状態も存在しません。
ユーザーの Out of Box Experience のタイムスタンプも、次のキーで Windows レジストリに追加されます。
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\Stats [EndTimeStamp]
このレジストリ値は、登録ページが OOBE に含まれているかどうかに関係なく作成されます。 タイムスタンプは UTC (協定世界時) 形式で書き込まれます。具体的には、レジストリにシリアル化されたデータの BLOB として書き込まれる SYSTEMTIME
値です。
顧客情報にアクセスして使用するには、次の手順を実行します。
- 公開キーと秘密キーのペアを生成し、その公開キーをイメージの
%systemroot%\system32\Oobe\Info
フォルダーに配置します。 - 暗号化された顧客データの収集。最初のログオンが完了してから約 30 分後に実行されるアプリまたはサービスを使用します。
- 暗号化を解除するために、データをサーバーに送信します。SSL を使用します。 次に、セッション キーの暗号化を解除して、顧客データの暗号化を解除できます。
公開/秘密キー ペアを生成する
顧客データを保護するには、公開キーと秘密キーのペアを生成し、公開キーを %systemroot%\system32\Oobe\Info
フォルダーに配置する必要があります。 複数の地域または複数の言語でイメージをデプロイする場合は、「Oobe.xml のしくみ」で説明されているように、地域または言語固有の Oobe.xml ファイルの場合と同じルールに従って、地域および言語固有のサブディレクトリのすぐ下に公開キーを配置する必要があります。
重要
顧客の PC に秘密キーを配置しないでください。 代わりに、アップロード後にデータの暗号化を解除できるように、サーバーに安全に保存する必要があります。 顧客が登録ページで [次へ] をクリックすると、Windows は公開キーを使用して %systemroot%\system32\Oobe\Info
フォルダーに Sessionkey.blob を作成します。 サービスまたは Microsoft Store アプリは、SSL を使用してデータをサーバーにアップロードする必要があります。 次に、セッション キーの暗号化を解除して、顧客データの暗号化を解除する必要があります。
%systemroot%\system32\Oobe\Info
フォルダーに公開キーがない場合は、登録ページが表示されません。
公開キーと秘密キーの生成
この一連の呼び出しを行って、公開キーと秘密キーを生成します。
CryptAcquireContext API を使用して暗号化コンテキストを取得します。 次の値を指定します。
pszProvider
はMS_ENH_RSA_AES_PROV
ですdwProvType
はPROV_RSA_AES
です
CryptGenKey API を使用して RSA crypt キーを生成します。 次の値を指定します。
Algid
はCALG_RSA_KEYX
ですdwFlags
はCRYPT_EXPORTABLE
です
CryptExportKey API を使用して、手順 2 で crypt キーの公開キー部分をシリアル化します。 次の値を指定します。
dwBlobType
はPUBLICKEYBLOB
です
標準の Windows のファイル管理機能を使用して、手順 3 のシリアル化された公開キー バイトをファイル Pubkey.blob に書き込みます。
CryptExportKey API を使用して、手順 2 の crypt キーの暗号化キー部分をシリアル化します。 次の値を指定します。
dwBlobType
はPRIVATEKEYBLOB
です
標準の Windows ファイル API を使用して、手順 5 でシリアル化された秘密キー バイトをファイル Prvkey.blob に書き込みます。
次のコード スニペットは、キーを生成する方法を示しています。
HRESULT CryptExportKeyHelper(_In_ HCRYPTKEY hKey, _In_opt_ HCRYPTKEY hExpKey, DWORD dwBlobType, _Outptr_result_bytebuffer_(*pcbBlob) BYTE **ppbBlob, _Out_ DWORD *pcbBlob);
HRESULT WriteByteArrayToFile(_In_ PCWSTR pszPath, _In_reads_bytes_(cbData) BYTE const *pbData, DWORD cbData);
// This method generates an OEM public and private key pair and writes it to Pubkey.blob and Prvkey.blob
HRESULT GenerateKeysToFiles()
{
// Acquire crypt provider. Use provider MS_ENH_RSA_AES_PROV and provider type PROV_RSA_AES to decrypt the blob from OOBE.
HCRYPTPROV hProv;
HRESULT hr = CryptAcquireContext(&hProv, L"OEMDecryptContainer", MS_ENH_RSA_AES_PROV,
PROV_RSA_AES, CRYPT_NEWKEYSET) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
if (hr == NTE_EXISTS)
{
hr = CryptAcquireContext(&hProv, L"OEMDecryptContainer", MS_ENH_RSA_AES_PROV,
PROV_RSA_AES, 0) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr))
{
// Call CryptGenKey to generate the OEM public and private key pair. OOBE expects the algorithm to be CALG_RSA_KEYX.
HCRYPTKEY hKey;
hr = CryptGenKey(hProv, CALG_RSA_KEYX, CRYPT_EXPORTABLE, &hKey) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
// Call CryptExportKeyHelper to serialize the public key into bytes.
BYTE *pbPubBlob;
DWORD cbPubBlob;
hr = CryptExportKeyHelper(hKey, NULL, PUBLICKEYBLOB, &pbPubBlob, &cbPubBlob);
if (SUCCEEDED(hr))
{
// Call CryptExportKey again to serialize the private key into bytes.
BYTE *pbPrvBlob;
DWORD cbPrvBlob;
hr = CryptExportKeyHelper(hKey, NULL, PRIVATEKEYBLOB, &pbPrvBlob, &cbPrvBlob);
if (SUCCEEDED(hr))
{
// Now write the public key bytes into the file pubkey.blob
hr = WriteByteArrayToFile(L"pubkey.blob", pbPubBlob, cbPubBlob);
if (SUCCEEDED(hr))
{
// And write the private key bytes into the file Prvkey.blob
hr = WriteByteArrayToFile(L"prvkey.blob", pbPrvBlob, cbPrvBlob);
}
HeapFree(GetProcessHeap(), 0, pbPrvBlob);
}
HeapFree(GetProcessHeap(), 0, pbPubBlob);
}
CryptDestroyKey(hKey);
}
CryptReleaseContext(hProv, 0);
}
return hr;
}
HRESULT CryptExportKeyHelper(_In_ HCRYPTKEY hKey, _In_opt_ HCRYPTKEY hExpKey, DWORD dwBlobType, _Outptr_result_bytebuffer_(*pcbBlob) BYTE **ppbBlob, _Out_ DWORD *pcbBlob)
{
*ppbBlob = nullptr;
*pcbBlob = 0;
// Call CryptExportKey the first time to determine the size of the serialized key.
DWORD cbBlob = 0;
HRESULT hr = CryptExportKey(hKey, hExpKey, dwBlobType, 0, nullptr, &cbBlob) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
// Allocate a buffer to hold the serialized key.
BYTE *pbBlob = reinterpret_cast<BYTE *>(CoTaskMemAlloc(cbBlob));
hr = (pbBlob != nullptr) ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
// Now export the key to the buffer.
hr = CryptExportKey(hKey, hExpKey, dwBlobType, 0, pbBlob, &cbBlob) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
*ppbBlob = pbBlob;
*pcbBlob = cbBlob;
pbBlob = nullptr;
}
CoTaskMemFree(pbBlob);
}
}
return hr;
}
HRESULT WriteByteArrayToFile(_In_ PCWSTR pszPath, _In_reads_bytes_(cbData) BYTE const *pbData, DWORD cbData)
{
bool fDeleteFile = false;
HANDLE hFile = CreateFile(pszPath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
HRESULT hr = (hFile == INVALID_HANDLE_VALUE) ? HRESULT_FROM_WIN32(GetLastError()) : S_OK;
if (SUCCEEDED(hr))
{
DWORD cbWritten;
hr = WriteFile(hFile, pbData, cbData, &cbWritten, nullptr) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
fDeleteFile = FAILED(hr);
CloseHandle(hFile);
}
if (fDeleteFile)
{
DeleteFile(pszPath);
}
return hr;
}
暗号化された顧客データの収集
Microsoft Store アプリを作成してプレインストールするか、初めてサインインした後に実行するサービスを記述して、次のことを行います。
- Windows.System.User 名前空間 からのユーザー名、および最初のサインインのローカル タイム スタンプを含む、暗号化された顧客データを収集します。
- そのデータ セットをサーバーにアップロードし、暗号化を解除して使用します。
Microsoft Store アプリを使用してデータを収集するには、そのアプリケーション ユーザー モデル ID (AUMID) を Microsoft-Windows-Shell-Setup | OOBE | OEMAppId 無人設定に割り当てます。 Windows は、デバイスにログオンする最初のユーザーに関連付けられる OEM アプリのアプリケーション データ フォルダーに、タイムスタンプ、ユーザー データ、セッション キー、およびチェック ボックスの状態データを渡します。 たとえば、そのユーザーの %localappdata%\packages\[OEM app package family name]\LocalState
です。
データをアップロードするサービスを作成して実行する場合は、ユーザーが [スタート] 画面にアクセスしてから少なくとも 30 分後にサービスを実行するように設定し、サービスを 1 回だけ実行する必要があります。 この時点でサービスを実行するように設定すると、ユーザーが初めて [スタート] 画面にアクセスしてアプリを探している間、サービスによってバックグラウンドでシステム リソースが消費されることがなくなります。 サービスでは、OOBE ディレクトリ内からデータを収集する必要があります。また、必要に応じて、タイムスタンプとユーザー名も収集する必要があります。 サービスでは、ユーザーの選択に応じて実行するアクションも決定する必要があります。 たとえば、ユーザーがマルウェア対策アプリの試用版を選択した場合、サービスでは、マルウェア対策アプリに依存してそれを実行するかどうか決定するのではなく、試用版を開始する必要があります。 または、別の例として、ユーザーが会社またはパートナー会社からの電子メールを選択した場合、サービスはその情報をマーケティング電子メールを処理する人に送信する必要があります。
サービスを記述する方法の詳細については、「Windows サービス アプリケーションの開発」を参照してください。
暗号化解除のためにサーバーにデータを送信する
サービスまたは Microsoft Store アプリは、SSL を使用してデータをサーバーにアップロードする必要があります。 次に、セッション キーの暗号化を解除して、顧客データの暗号化を解除する必要があります。
データの暗号化を解除する
データの暗号化を解除するには、次の一連の呼び出しを行います。
CryptAcquireContext API を使用して暗号化コンテキストを取得します。 次の値を指定します。
pszProvider
はMS_ENH_RSA_AES_PROV
ですdwProvType
はPROV_RSA_AES
です
標準の Windows ファイル API を使用して、ディスクから OEM 秘密キー ファイル (Prvkey.blob) を読み取ります。
CryptImportKey API を使用して、秘密キーのバイトを暗号化キーに変換します。
標準の Windows ファイル API を使用して、ディスクから OOBE 生成のセッション キー ファイル (Sessionkey.blob) を読み取ります。
手順 3 の秘密キーを使用して、CryptImportKey API を使用して、セッション キー バイトを暗号化キーに変換します。
エクスポート キー (hPubKey) は、手順 3 でインポートされた秘密キーです。
標準の Windows ファイル API を使用して、ディスクから OOBE で作成された、暗号化されたユーザー データ (Userdata.blob) を読み取ります。
(手順 5 の) セッション キーを使用し、CryptDecrypt を使用してユーザー データの暗号化を解除します。
次のコード スニペットは、データの暗号化を解除する方法を示しています。
HRESULT DecryptHelper(_In_reads_bytes_(cbData) BYTE *pbData, DWORD cbData, _In_ HCRYPTKEY hPrvKey, _Outptr_result_bytebuffer_(*pcbPlain) BYTE **ppbPlain, _Out_ DWORD *pcbPlain);
HRESULT ReadFileToByteArray(_In_ PCWSTR pszPath, _Outptr_result_bytebuffer_(*pcbData) BYTE **ppbData, _Out_ DWORD *pcbData);
// This method uses the specified Userdata.blob (pszDataFilePath), Sessionkey.blob (pszSessionKeyPath), and Prvkey.blob (pszPrivateKeyPath)
// and writes the plaintext XML user data to Plaindata.xml
HRESULT UseSymmetricKeyFromFileToDecrypt(_In_ PCWSTR pszDataFilePath, _In_ PCWSTR pszSessionKeyPath, _In_ PCWSTR pszPrivateKeyPath)
{
// Acquire crypt provider. Use provider MS_ENH_RSA_AES_PROV and provider type PROV_RSA_AES to decrypt the blob from OOBE.
HCRYPTPROV hProv;
HRESULT hr = CryptAcquireContext(&hProv, L"OEMDecryptContainer", MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_NEWKEYSET) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
if (hr == NTE_EXISTS)
{
hr = CryptAcquireContext (&hProv, L"OEMDecryptContainer", MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr))
{
// Read in the OEM private key file.
BYTE *pbPrvBlob;
DWORD cbPrvBlob;
hr = ReadFileToByteArray(pszPrivateKeyPath, &pbPrvBlob, &cbPrvBlob);
if (SUCCEEDED(hr))
{
// Convert the private key file bytes into an HCRYPTKEY.
HCRYPTKEY hKey;
hr = CryptImportKey(hProv, pbPrvBlob, cbPrvBlob, 0, 0, &hKey) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
// Read in the encrypted session key generated by OOBE.
BYTE *pbSymBlob;
DWORD cbSymBlob;
hr = ReadFileToByteArray(pszSessionKeyPath, &pbSymBlob, &cbSymBlob);
if (SUCCEEDED(hr))
{
// Convert the encrypted session key file bytes into an HCRYPTKEY.
// This uses the OEM private key to decrypt the session key file bytes.
HCRYPTKEY hSymKey;
hr = CryptImportKey(hProv, pbSymBlob, cbSymBlob, hKey, 0, &hSymKey) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
// Read in the encrypted user data written by OOBE.
BYTE *pbCipher;
DWORD dwCipher;
hr = ReadFileToByteArray(pszDataFilePath, &pbCipher, &dwCipher);
if (SUCCEEDED(hr))
{
// Use the session key to decrypt the encrypted user data.
BYTE *pbPlain;
DWORD dwPlain;
hr = DecryptHelper(pbCipher, dwCipher, hSymKey, &pbPlain, &dwPlain);
if (SUCCEEDED(hr))
{
hr = WriteByteArrayToFile(L"plaindata.xml", pbPlain, dwPlain);
HeapFree(GetProcessHeap(), 0, pbPlain);
}
HeapFree(GetProcessHeap(), 0, pbCipher);
}
CryptDestroyKey(hSymKey);
}
HeapFree(GetProcessHeap(), 0, pbSymBlob);
}
else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
{
wcout << L"Couldn't find session key file [" << pszSessionKeyPath << L"]" << endl;
}
CryptDestroyKey(hKey);
}
HeapFree(GetProcessHeap(), 0, pbPrvBlob);
}
else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
{
wcout << L"Couldn't find private key file [" << pszPrivateKeyPath << L"]" << endl;
}
CryptReleaseContext(hProv, 0);
}
return hr;
}
HRESULT DecryptHelper(_In_reads_bytes_(cbData) BYTE *pbData, DWORD cbData, _In_ HCRYPTKEY hPrvKey, _Outptr_result_bytebuffer_(*pcbPlain) BYTE **ppbPlain, _Out_ DWORD *pcbPlain)
{
BYTE *pbCipher = reinterpret_cast<BYTE *>(HeapAlloc(GetProcessHeap(), 0, cbData));
HRESULT hr = (pbCipher != nullptr) ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
// CryptDecrypt will write the actual length of the plaintext to cbPlain.
// Any block padding that was added during CryptEncrypt won't be counted in cbPlain.
DWORD cbPlain = cbData;
memcpy(pbCipher, pbData, cbData);
hr = ResultFromWin32Bool(CryptDecrypt(hPrvKey,
0,
TRUE,
0,
pbCipher,
&cbPlain));
if (SUCCEEDED(hr))
{
*ppbPlain = pbCipher;
*pcbPlain = cbPlain;
pbCipher = nullptr;
}
HeapFree(GetProcessHeap(), 0, pbCipher);
} return hr;
}
HRESULT ReadFileToByteArray(_In_ PCWSTR pszPath, _Outptr_result_bytebuffer_(*pcbData) BYTE **ppbData, _Out_ DWORD *pcbData)
{
*ppbData = nullptr;
*pcbData = 0;
HANDLE hFile = CreateFile(pszPath, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
HRESULT hr = (hFile == INVALID_HANDLE_VALUE) ? HRESULT_FROM_WIN32(GetLastError()) : S_OK;
if (SUCCEEDED(hr))
{
DWORD cbSize = GetFileSize(hFile, nullptr);
hr = (cbSize != INVALID_FILE_SIZE) ? S_OK : ResultFromKnownLastError();
if (SUCCEEDED(hr))
{
BYTE *pbData = reinterpret_cast<BYTE *>(CoTaskMemAlloc(cbSize));
hr = (pbData != nullptr) ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
DWORD cbRead;
hr = ReadFile(hFile, pbData, cbSize, &cbRead, nullptr) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
*ppbData = pbData;
*pcbData = cbSize;
pbData = nullptr;
}
CoTaskMemFree(pbData);
}
}
CloseHandle(hFile);
}
return hr;
}