0

I'm writing a small console program to test out DPAPI based on the constraints I have to work with for a project (all wstring, need to output encrypted data in base64) and ran into an issue where if I call LocalFree on the pbData of the CryptProtectData output blob, the decrypt would fail.
My Encrypt() and Decrypt() helpers:

std::wstring Encrypt(std::wstring input)
{
    char *inputBuf = new char[input.size() + 1];
    size_t temp = 0;
    wcstombs_s(&temp, inputBuf, input.size() + 1, input.c_str(), input.size());

    CRYPT_INTEGER_BLOB inputBlob;
    inputBlob.cbData = strlen(inputBuf) + 1;
    inputBlob.pbData = (BYTE*)inputBuf;
    CRYPT_INTEGER_BLOB outputBlob;

    CryptProtectData(&inputBlob, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &outputBlob);

    BYTE *outputBlobPtr = (BYTE *)(&outputBlob);
    DWORD encodedLen = 0;
    CryptBinaryToStringW(outputBlobPtr, sizeof(CRYPT_INTEGER_BLOB), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &encodedLen);
    wchar_t *outputBuf = new wchar_t[encodedLen];
    CryptBinaryToStringW(outputBlobPtr, sizeof(CRYPT_INTEGER_BLOB), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, outputBuf, &encodedLen);

    std::wstring output = std::wstring(outputBuf, encodedLen);

    LocalFree(outputBlob.pbData); // <-- This is the offending line
    delete[] inputBuf;
    delete[] outputBuf;

    return output;
}

std::wstring Decrypt(std::wstring input)
{
    wchar_t *inputBuf = new wchar_t[input.size() + 1];
    wcscpy_s(inputBuf, input.size() + 1, input.c_str());

    DWORD decodedLen = 0;
    CryptStringToBinaryW(inputBuf, wcslen(inputBuf), CRYPT_STRING_BASE64, NULL, &decodedLen, NULL, NULL);
    BYTE *encryptedBlobPtr = new BYTE[decodedLen];
    if (CryptStringToBinaryW(inputBuf, wcslen(inputBuf), CRYPT_STRING_BASE64, encryptedBlobPtr, &decodedLen, NULL, NULL) == 0)
    {
        std::cout << "String to blob conversion error: " << GetLastError() << std::endl;
        return std::wstring();
    }

    CRYPT_INTEGER_BLOB encryptedBlob = *((CRYPT_INTEGER_BLOB *)encryptedBlobPtr);
    CRYPT_INTEGER_BLOB decryptedBlob;

    if (CryptUnprotectData(&encryptedBlob, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &decryptedBlob) == 0)
    {
        std::cout << "Decryption error: " << GetLastError() << std::endl;
        return std::wstring();
    }

    wchar_t *outputBuf = new wchar_t[decryptedBlob.cbData + 1];
    size_t temp = 0;
    mbstowcs_s(&temp, outputBuf, decryptedBlob.cbData + 1, (char *)decryptedBlob.pbData, decryptedBlob.cbData);

    std::wstring output = std::wstring(outputBuf, decryptedBlob.cbData + 1);

    LocalFree(decryptedBlob.pbData);
    delete[] inputBuf;
    delete[] encryptedBlobPtr;
    delete[] outputBuf;

    return output;
}

If the line is commented out, then calling Encrypt(L"Some string"), storing the result in a wstring, and then calling Decrypt(resultOfEncrypt) would get me "Some string" back. If the line is there there, then the program fails at CryptUnprotectData() with GetLastError() == 13 (invalid data).
Am I misunderstanding how DPAPI or LocalFree() works? Why must the outputBlob be in memory if I'm already preserving it by converting it to a string?

DennisKRQ
  • 175
  • 1
  • 6
  • 1
    Dangling pointer bug, the CryptBinaryToStringW() function encodes a pointer value. You need to encode outputBlob.pbData – Hans Passant May 17 '19 at 16:57
  • Nice! Changing the binary-string conversions to convert the pbData pointer with size set to the cbData value worked like a charm! – DennisKRQ May 17 '19 at 18:13

0 Answers0