chandrashekhar
chandrashekhar

Reputation: 61

How to encrypt and decrypt a const char* in WinRT

I have been trying to write encrypt and decrypt functions whose signatures require the input and the output strings to be void* type only. The code works fine if the inputs can be specified as IBuffer^ but in the other case the source string and the encrypted->decrypted string do not match.

CodeIBuffer^ byteArrayToIBufferPtr(byte *source, int size)
{
    Platform::ArrayReference<uint8> blobArray(source, size);
    IBuffer ^buffer = CryptographicBuffer::CreateFromByteArray(blobArray);
    return buffer;
}

byte* IBufferPtrToByteArray(IBuffer ^buffer)
{
    Array<unsigned char,1U> ^platArray = ref new Array<unsigned char,1U>(256);
    CryptographicBuffer::CopyToByteArray(buffer,&platArray);

    byte *dest = platArray->Data;
    return dest;
}

int DataEncryption::encryptData(EncryptionAlgorithm algo, int keySize, void* srcData, const unsigned int srcSize,
        void*& encData, unsigned int& encSize)
{

    LOG_D(TAG, "encryptData()");

    if(srcData == nullptr)
    {
        LOG_E(TAG,"");
        return DataEncryption::RESULT_EMPTY_DATA_ERROR;
    }
    if(srcSize == 0)
    {
        LOG_E(TAG,"");
        return DataEncryption::RESULT_SIZE_ZERO_ERROR;
    }

    IBuffer^ encrypted;
    IBuffer^ buffer;
    IBuffer^ iv = nullptr;
    String^ algName;
    bool cbc = false;

    switch (algo)
    {
    case DataEncryption::ENC_DEFAULT:
        algName = "AES_CBC";
        cbc = true;
        break;
    default:
        break;
    }

    // Open the algorithm provider for the algorithm specified on input.
    SymmetricKeyAlgorithmProvider^ Algorithm = SymmetricKeyAlgorithmProvider::OpenAlgorithm(algName);

    // Generate a symmetric key.
    IBuffer^ keymaterial = CryptographicBuffer::GenerateRandom((keySize + 7) / 8);
    CryptographicKey^ key;

    try
    {
        key = Algorithm->CreateSymmetricKey(keymaterial);
    }
    catch(InvalidArgumentException^ e)
    {
        LOG_E(TAG,"encryptData(): Could not create key.");
        return DataEncryption::RESULT_ERROR;
    }

    // CBC mode needs Initialization vector, here just random data.
    // IV property will be set on "Encrypted".
    if (cbc)
        iv = CryptographicBuffer::GenerateRandom(Algorithm->BlockLength);

    // Set the data to encrypt. 
    IBuffer ^srcDataBuffer = byteArrayToIBufferPtr(static_cast<byte*>(srcData),256);

    // Encrypt and create an authenticated tag.
    encrypted = CryptographicEngine::Encrypt(key, srcDataBuffer, iv);

    //encData = encrypted;
    byte *bb = IBufferPtrToByteArray(encrypted);
    encData = IBufferPtrToByteArray(encrypted);
    encSize = encrypted->Length;

    return DataEncryption::RESULT_SUCCESS;
}


int DataEncryption::decryptData(EncryptionAlgorithm algo, int keySize, void* encData, const unsigned int encSize,
        void*& decData, unsigned int& decSize)
{
    LOG_D(TAG, "decryptData()");

    if(encData == nullptr)
    {
        LOG_E(TAG,"");
        return DataEncryption::RESULT_EMPTY_DATA_ERROR;
    }
    if(encSize == 0)
    {
        LOG_E(TAG,"");
        return DataEncryption::RESULT_SIZE_ZERO_ERROR;
    }

    IBuffer^ encrypted;
    IBuffer^ decrypted;
    IBuffer^ iv = nullptr;
    String^ algName;
    bool cbc = false;

    switch (algo)
    {
    case DataEncryption::ENC_DEFAULT:
        algName = "AES_CBC";
        cbc = true;
        break;
    default:
        break;
    }

    // Open the algorithm provider for the algorithm specified on input.
    SymmetricKeyAlgorithmProvider^ Algorithm = SymmetricKeyAlgorithmProvider::OpenAlgorithm(algName);

    // Generate a symmetric key.
    IBuffer^ keymaterial = CryptographicBuffer::GenerateRandom((keySize + 7) / 8);
    CryptographicKey^ key;

    try
    {
        key = Algorithm->CreateSymmetricKey(keymaterial);
    }
    catch(InvalidArgumentException^ e)
    {
        LOG_E(TAG,"encryptData(): Could not create key.");
        return DataEncryption::RESULT_ERROR;
    }

    // CBC mode needs Initialization vector, here just random data.
    // IV property will be set on "Encrypted".
    if (cbc)
        iv = CryptographicBuffer::GenerateRandom(Algorithm->BlockLength);

    // Set the data to decrypt. 
    byte *cc = static_cast<byte*>(encData);
    IBuffer ^encDataBuffer = byteArrayToIBufferPtr(cc,256);
    // Decrypt and verify the authenticated tag.
    decrypted = CryptographicEngine::Decrypt(key, encDataBuffer, iv);

    byte *bb = IBufferPtrToByteArray(decrypted);
    decData = IBufferPtrToByteArray(decrypted);

    decSize = decrypted->Length;

    return DataEncryption::RESULT_SUCCESS;
}

Upvotes: 0

Views: 1854

Answers (1)

Jim Dovey
Jim Dovey

Reputation: 11156

I'm guessing that the problem is with this function:

byte* IBufferPtrToByteArray(IBuffer ^buffer)
{
    Array<unsigned char,1U> ^platArray = ref new Array<unsigned char,1U>(256);
    CryptographicBuffer::CopyToByteArray(buffer,&platArray);

    byte *dest = platArray->Data;
    return dest;
}

What you're doing there is allocating a new Platform::Array<byte>^ with 1 reference, then getting a pointer to its internally-managed storage, then returning that pointer-- at which point the Array is being dereferenced and is thus deallocating its underlying storage. Thus the pointer you return refers to freed memory. The next allocation is likely to overwrite those bytes.

What you'll need to do is take the return-by-reference Array<byte>^ from CopyToByteArray() (which creates a new Array, presumably wrapping the bytes of the input IBuffer^, and returns it) and copy that array's contents.

Your end result will function similarly to this snippet from the Readium SDK project, which takes a std::string instance, hashes it using SHA-1, and copies the hash data into a member variable uint8_t _key[KeySize]:

using namespace ::Platform;
using namespace ::Windows::Foundation::Cryptography;
using namespace ::Windows::Foundation::Cryptography::Core;

auto byteArray = ArrayReference<byte>(reinterpret_cast<byte*>(const_cast<char*>(str.data())), str.length());
auto inBuf = CryptographicBuffer::CreateFromByteArray(byteArray);
auto keyBuf = HashAlgorithmProvider::OpenAlgorithm(HashAlgorithmNames::Sha1)->HashData(inBuf);

Array<byte>^ outArray = nullptr;
CryptographicBuffer::CopyToByteArray(keyBuf, &outArray);
memcpy_s(_key, KeySize, outArray->Data, outArray->Length);

The steps:

  1. Create an ArrayReference<byte> corresponding to the bytes in the std::string (no copying).
  2. Pass that to CryptographicBuffer::CreateFromByteArray() to get your IBuffer^. Still no copying of data.
  3. Call your hash/encryption function, passing the IBuffer^ you just made. You get another IBuffer^ in return, which may or may not be using the exact same storage (that's really up to the implementation of the algorithm, I think).
  4. Create a variable of type Array<byte>^. Don't allocate an object, you're going to be given one by reference.
  5. Pass the address of that object into CryptographicBuffer::CopyToByteArray() to receive a copy of your key data.
  6. While that Array^ remains valid, copy its bytes into your native array.

Upvotes: 1

Related Questions