Freigeben über


Schützen und Erfassen von Benutzerdaten

Wenn Kunden Informationen in die OEM-Registrierungsseiten eingibt, werden die folgenden Dateien erstellt, wenn sie OOBE abschließen:

  • Userdata.blob. Eine verschlüsselte XML-Datei, die alle Werte aller benutzerkonfigurierbaren Elementen auf den Registrierungsseiten enthält, einschließlich Kundeninformationsfeldern und Kontrollkästchenzuständen.
  • SessionKey.blob. Wird während der Verschlüsselung von Userdata.blob generiert. Enthält einen Sitzungsschlüssel, der für den Entschlüsselungsprozess erforderlich ist.
  • Userchoices.xml. Eine nicht verschlüsselte XML-Datei, die die Beschriftungen und Werte aller Kontrollkästchen enthält, die auf den Registrierungsseiten enthalten sind.

Hinweis

Wenn Kunden auf der ersten Registrierungsseite auf Skip klicken, werden keine Daten in diese Dateien geschrieben oder darin gespeichert, nicht einmal die Standardzustände der Kontrollkästchen.

Der Zeitstempel der Out-of-Box-Experience der Benutzer wird ebenfalls der Windows-Registrierung unter diesem Schlüssel hinzugefügt:

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\Stats [EndTimeStamp]

Dieser Registrierungswert wird unabhängig davon erstellt, ob die Registrierungsseiten in OOBE enthalten sind. Der Zeitstempel wird im UTC-Format (koordinierte Weltzeit) geschrieben. Genauer gesagt handelt es sich um einen SYSTEMTIME-Wert, der als serialisierter Datenblob in die Registrierung geschrieben wird.

Führen Sie die folgenden Schritte aus, um auf Kundeninformationen zuzugreifen und sie zu verwenden:

  1. Generieren Sie ein Schlüsselpaar aus öffentlichem und privatem Schlüssel, und platzieren Sie den öffentlichen Schlüssel im Ordner %systemroot%\system32\Oobe\Info des Abbilds.
  2. Erfassen Sie die verschlüsselten Kundendaten mithilfe einer App oder eines Diensts, der ungefähr 30 Minuten nach Abschluss der ersten Anmeldung ausgeführt wird.
  3. Senden Sie die Daten zum Entschlüsseln an Ihren Server und verwenden Sie dazu SSL. Anschließend können Sie den Sitzungsschlüssel entschlüsseln, um die Kundendaten zu entschlüsseln.

Generieren eines öffentlichen/privaten Schlüsselpaars

Um Kundendaten zu schützen, müssen Sie ein Schlüsselpaar aus öffentlichem und privatem Schlüssel generieren, und der öffentliche Schlüssel muss im Ordner %systemroot%\system32\Oobe\Info platziert werden. Wenn Sie Images in mehreren Regionen oder in mehreren Sprachen bereitstellen, sollten Sie den öffentlichen Schlüssel direkt unter regions- und sprachspezifischen Unterverzeichnissen platzieren, wobei Sie die gleichen Regeln wie für regions- oder sprachspezifische Oobe.xml-Dateien anwenden, wie in Funktionsweise von Oobe.xml beschrieben.

Wichtig

Sie dürfen den privaten Schlüssel niemals auf dem PC des Kunden ablegen. Stattdessen sollte er sicher auf Ihren Servern gespeichert werden, damit die Daten nach dem Hochladen entschlüsselt werden können. Wenn Kunden auf den Registrierungsseiten auf „Weiter“ klicken, verwendet Windows den öffentlichen Schlüssel, um Sessionkey.blob im Ordner %systemroot%\system32\Oobe\Info zu erstellen. Ihr Dienst oder Ihre Microsoft Store-App sollte die Daten unter Verwendung von SSL auf Ihren Server hochladen. Anschließend müssen Sie den Sitzungsschlüssel entschlüsseln, um die Kundendaten zu entschlüsseln.

Wenn im Ordner %systemroot%\system32\Oobe\Info kein öffentlicher Schlüssel vorhanden ist, werden die Registrierungsseiten nicht angezeigt.

Generieren von öffentlichen und privaten Schlüsseln

Führen Sie diese Abfolge von Aufrufen aus, um die öffentlichen und privaten Schlüssel zu generieren.

  1. Rufen Sie den Kryptografiekontext mit der CryptAcquireContext-API ab. Geben Sie die folgenden Werte an:

    • pszProvider ist gleich MS_ENH_RSA_AES_PROV.
    • dwProvType ist gleich PROV_RSA_AES.
  2. Generieren Sie mit der CryptGenKey-API RSA-Schlüssel. Geben Sie die folgenden Werte an:

    • Algid ist gleich CALG_RSA_KEYX.
    • dwFlags ist gleich CRYPT_EXPORTABLE.
  3. Serialisieren Sie den öffentlichen Schlüsselteil des Kryptografieschlüssels aus Schritt 2 mit der CryptExportKey-API. Geben Sie diesen Wert an:

    • dwBlobType ist PUBLICKEYBLOB.
  4. Schreiben Sie die serialisierten Bytes mit dem öffentlichen Schlüssel aus Schritt 3 mit den standardmäßigen Windows-Dateiverwaltungsfunktionen in die Datei Pubkey.blob.

  5. Serialisieren Sie den privaten Schlüsselteil des Kryptografieschlüssels aus Schritt 2 mit der CryptExportKey-API. Geben Sie diesen Wert an

    • dwBlobType ist PRIVATEKEYBLOB.
  6. Schreiben Sie die serialisierten Bytes mit dem privaten Schlüssel aus Schritt 5 mit der standardmäßigen Windows-Datei-API in die Datei Prvkey.blob.

Dieser Codeausschnitt zeigt, wie Sie die Schlüssel generieren:

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;
}

Erfassen verschlüsselter Kundendaten

Erstellen und Installieren Sie vorab eine Microsoft Store-App oder schreiben Sie einen Dienst, der nach der ersten Anmeldung ausgeführt werden soll, um Folgendes auszuführen:

  1. Erfassen Sie die verschlüsselten Kundendaten einschließlich des Benutzernamens aus dem Windows.System.User-Namespace sowie den lokalen Zeitstempel der ersten Anmeldung.
  2. Laden Sie diesen Datensatz zur Entschlüsselung und Verwendung auf Ihren Server hoch.

Um eine Microsoft Store-App zum Sammeln der Daten zu verwenden, weisen Sie der Einstellung Microsoft-Windows-Shell-Setup | OOBE | OEMAppId „Unattend“ die Anwendungsbenutzermodell-ID (AUMID) zu. Windows übergibt den Zeitstempel, die Benutzerdaten, den Sitzungsschlüssel und die Kontrollkästchenstatusdaten an den Anwendungsdatenordner der OEM-App, der dem Benutzer zugeordnet ist, der sich zuerst beim Gerät anmeldet. Beispiel: %localappdata%\packages\[OEM app package family name]\LocalState für diesen Benutzer.

Wenn Sie einen Dienst zum Hochladen der Daten erstellen und ausführen, sollten Sie den Dienst so einstellen, dass er mindestens 30 Minuten, nachdem der Benutzer den Startbildschirm aufgerufen hat, ausgeführt wird, und Sie sollten den Dienst nur einmal ausführen. Wenn Sie Ihren Dienst so konfigurieren, dass er zu diesem Zeitpunkt ausgeführt wird, wird sichergestellt, dass Ihr Dienst keine Systemressourcen im Hintergrund verbraucht, während die Benutzer zum ersten Mal die Gelegenheit haben, den Startbildschirm und ihre Anwendungen zu erkunden. Der Dienst muss die Daten aus dem OOBE-Verzeichnis sowie den Zeitstempel und den Benutzernamen erfassen, sofern zutreffend. Der Dienst sollte auch bestimmen, welche Aktionen als Reaktion auf die Auswahl der Benutzer ausgeführt werden sollen. Wenn sich Benutzer beispielsweise für die Testversion einer Anti-Malware-App entschieden haben, sollte Ihr Dienst die Testversion starten, anstatt darauf zu warten, dass die Anti-Malware-App entscheidet, ob sie ausgeführt werden soll. Ein anderes Beispiel wäre, wenn sich Benutzer dafür entschieden haben, E-Mails von Ihrem Unternehmen oder Partnerunternehmen zu erhalten, sollte Ihr Dienst diese Informationen an diejenigen übermitteln, die für Ihre Marketing-E-Mails zuständig sind.

Weitere Informationen zum Schreiben eines Diensts finden Sie unter Entwickeln von Windows-Dienstanwendungen.

Senden von Daten an einen Server zur Entschlüsselung

Ihr Dienst oder Ihre Microsoft Store-App sollte die Daten unter Verwendung von SSL auf Ihren Server hochladen. Anschließend müssen Sie zum Entschlüsseln der Kundendaten den Sitzungsschlüssel entschlüsseln.

Entschlüsseln der Daten

Führen Sie diese Abfolge von Aufrufen aus, um die Daten zu entschlüsseln:

  1. Rufen Sie den Kryptografiekontext mit der CryptAcquireContext-API ab. Geben Sie die folgenden Werte an:

    • pszProvider ist gleich MS_ENH_RSA_AES_PROV.
    • dwProvType ist gleich PROV_RSA_AES.
  2. Lesen Sie die OEM-Datei für private Schlüssel (Prvkey.blob) mit der standardmäßigen Windows-Datei-API vom Datenträger.

  3. Konvertieren Sie die Bytes mit dem privaten Schlüssel mit der CryptImportKey-API in einen Kryptografieschlüssel.

  4. Lesen Sie die vom OOBE generierte Sitzungsschlüsseldatei (Sessionkey.blob) mit der standardmäßigen Windows-Datei-API vom Datenträger.

  5. Verwenden Sie den privaten Schlüssel aus Schritt 3, um die Bytes mit dem Sitzungsschlüssel mit der CryptImportKey-API in einen Kryptografieschlüssel zu konvertieren.

  6. Der Exportschlüssel (hPubKey) ist der in Schritt 3 importierte private Schlüssel.

  7. Lesen Sie die von OOBE geschriebenen verschlüsselten Benutzerdaten (Userdata.blob) mit der standardmäßigen Windows-Datei-API vom Datenträger.

  8. Verwenden Sie den Sitzungsschlüssel (aus Schritt 5), um die Benutzerdaten mit CryptDecrypt zu entschlüsseln.

Dieser Codeausschnitt zeigt, wie Sie die Daten entschlüsseln:

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;
}