Protección y recopilación de datos de usuario
Si un cliente escribe información en las páginas de registro de OEM, se crean los siguientes archivos cuando completan OOBE:
- Userdata.blob. Un archivo XML cifrado que contiene todos los valores de todos los elementos configurables por el usuario en las páginas de registro, incluidos los campos de información del cliente y los estados de casilla.
- SessionKey.blob. Se genera durante el cifrado de Userdata.blob. Contiene una clave de sesión necesaria para el proceso de descifrado.
- Userchoices.xml. Un archivo XML sin cifrar que contiene las etiquetas y los valores de casilla de todas las casillas incluidas en las páginas de registro.
Nota
Si un cliente hace clic en la primera página de registro, no se escriben ni almacenan datos en estos archivos, ni siquiera los estados predeterminados Skip
de la casilla.
La marca de tiempo de la experiencia lista para usar del usuario también se agrega al Registro de Windows bajo esta clave:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\Stats [EndTimeStamp]
Este valor del Registro se crea independientemente de si las páginas de registro se incluyen en OOBE. La marca de tiempo está escrita en formato UTC (hora universal coordinada); en concreto, es un SYSTEMTIME
valor escrito como un blob serializado de datos en el Registro.
Para acceder y usar la información del cliente, siga estos pasos:
-
Genere un par de claves pública o privada y coloque la clave pública en la
%systemroot%\system32\Oobe\Info
carpeta de la imagen. - Recopile los datos cifrados del cliente mediante una aplicación o un servicio que se ejecute aproximadamente 30 minutos después de que se complete el primer inicio de sesión.
- Envíe los datos al servidor para el descifrado mediante SSL. A continuación, puede descifrar la clave de sesión para descifrar los datos del cliente.
Generación de un par de claves públicas y privadas
Para proteger los datos de los clientes, debe generar un par de claves pública o privada y la clave pública debe colocarse en la %systemroot%\system32\Oobe\Info
carpeta . Si va a implementar imágenes en varias regiones o en varios idiomas, debe colocar la clave pública directamente en subdirectorios específicos del idioma y de la región, siguiendo las mismas reglas que para los archivos de Oobe.xml específicos del idioma o región, tal como se describe en Funcionamiento de Oobe.xml.
Importante
Nunca debe colocar la clave privada en el equipo del cliente. En su lugar, debe almacenarse de forma segura en los servidores para que los datos se puedan descifrar después de cargarlos. Si un cliente hace clic en Siguiente en las páginas de registro, Windows usa la clave pública para crear Sessionkey.blob en la %systemroot%\system32\Oobe\Info
carpeta. El servicio o la aplicación de Microsoft Store deben cargar los datos en el servidor mediante SSL. A continuación, debe descifrar la clave de sesión para descifrar los datos del cliente.
Si no hay ninguna clave pública en la %systemroot%\system32\Oobe\Info
carpeta, no se muestran las páginas de registro.
Generación de claves públicas y privadas
Realice esta secuencia de llamadas para generar las claves públicas y privadas.
Adquiera el contexto de cifrado mediante la API CryptAcquireContext. Proporcione estos valores:
-
pszProvider
esMS_ENH_RSA_AES_PROV
-
dwProvType
esPROV_RSA_AES
-
Genere la clave de cifrado RSA mediante la API CryptGenKey. Proporcione estos valores:
-
Algid
esCALG_RSA_KEYX
-
dwFlags
esCRYPT_EXPORTABLE
-
Serialice la parte de clave pública de la clave de cifrado del paso 2 mediante la API CryptExportKey. Proporcione este valor:
-
dwBlobType
esPUBLICKEYBLOB
.
-
Escriba los bytes de clave pública serializados del paso 3 en el archivo Pubkey.blob mediante las funciones estándar de administración de archivos de Windows.
Serialice la parte de clave privada de la clave de cifrado del paso 2 mediante la API CryptExportKey. Proporcione este valor.
-
dwBlobType
esPRIVATEKEYBLOB
.
-
Escriba los bytes de clave privada serializados del paso 5 en el archivo Prvkey.blob mediante la API de archivos de Windows estándar.
Este fragmento de código muestra cómo generar las claves:
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;
}
Recopilación de datos cifrados de clientes
Cree e instale previamente una aplicación de Microsoft Store o escriba un servicio para que se ejecute después del primer inicio de sesión en:
- Recopile los datos cifrados del cliente, incluido el nombre de usuario del espacio de nombres Windows.System.User, así como la marca de tiempo local del primer inicio de sesión.
- Cargue ese conjunto de datos en el servidor para descifrarlo y usarlo.
Para usar una aplicación de Microsoft Store para recopilar los datos, asigne su identificador de modelo de usuario de aplicación (AUMID) a Microsoft-Windows-Shell-Setup | OOBE | Configuración desatendida de OEMAppId . Windows pasará la marca de tiempo, los datos de usuario, la clave de sesión y los datos de estado de casilla a la carpeta de datos de la aplicación oem, que está asociada al primer usuario para iniciar sesión en el dispositivo. Por ejemplo, %localappdata%\packages\[OEM app package family name]\LocalState
para ese usuario.
Si crea y ejecuta un servicio para cargar los datos, debe establecer que el servicio se ejecute al menos 30 minutos después de que el usuario llegue a la pantalla Inicio y solo ejecute el servicio una vez. Establecer el servicio para que se ejecute en este momento garantiza que el servicio no consumirá recursos del sistema en segundo plano mientras los usuarios obtienen su primera oportunidad de explorar la pantalla Inicio y sus aplicaciones. El servicio debe recopilar los datos desde el directorio OOBE, así como la marca de tiempo y el nombre de usuario, según corresponda. El servicio también debe determinar qué acciones realizar en respuesta a las opciones del usuario. Por ejemplo, si el usuario optó por una prueba de aplicación antimalware, el servicio debe iniciar la prueba en lugar de confiar en la aplicación antimalware para decidir si debe ejecutarse. O bien, como otro ejemplo, si el usuario optó por correos electrónicos de su empresa o empresas asociadas, su servicio debe comunicar esa información a quien controle sus correos electrónicos de marketing.
Para obtener más información sobre cómo escribir un servicio, consulta Desarrollar aplicaciones de servicio de Windows.
Envío de datos al servidor para el descifrado
El servicio o la aplicación de Microsoft Store deben cargar los datos en el servidor mediante SSL. A continuación, debe descifrar la clave de sesión para descifrar los datos del cliente.
Descifrar los datos
Realice esta secuencia de llamadas para descifrar los datos:
Adquiera el contexto de cifrado mediante la API CryptAcquireContext. Proporcione estos valores:
-
pszProvider
esMS_ENH_RSA_AES_PROV
-
dwProvType
esPROV_RSA_AES
-
Lea el archivo de clave privada OEM (Prvkey.blob) desde el disco mediante la API de archivos de Windows estándar.
Convierta los bytes de clave privada en una clave de cifrado mediante la API CryptImportKey.
Lea el archivo de clave de sesión generado por OOBE (Sessionkey.blob) desde el disco mediante la API de archivos de Windows estándar.
Use la clave privada del paso 3 para convertir los bytes de la clave de sesión en una clave de cifrado mediante la API CryptImportKey.
La clave de exportación (hPubKey) es la clave privada importada en el paso 3.
Lea los datos de usuario cifrados escritos por OOBE (Userdata.blob) desde el disco mediante la API de archivos de Windows estándar.
Use la clave de sesión (del paso 5) para descifrar los datos de usuario mediante CryptDecrypt.
Este fragmento de código muestra cómo descifrar los datos:
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;
}