-2

I'm confused about how to resize the cipher_buf and the buffer which will contain the encrypted/decrypted string.

And how do I convert the encrypted buffer to base64? I'll need a base64 encoder lib or there's an API for this on OpenSSL?

#include <openssl/aes.h>
#include <openssl/evp.h>
#include <Windows.h>

void decryptOrEncrypt()
{
    bool encrypt = true;

    std::string str = "testing testing";

    const int MAX_BUFFER_SIZE = str.length();

    int out_len;
    EVP_CIPHER_CTX * ctx = EVP_CIPHER_CTX_new();

    std::string key = "abcdabcdabcdabcd";
    std::string iv = "abcdabcdabcdabcd";

    size_t len = key.size();
    byte* keyPtr = (byte*)key.data();
    byte* ivPtr = (byte*)iv.data();

    EVP_CipherInit(ctx, EVP_aes_128_cbc(), keyPtr, ivPtr , encrypt);
    int blocksize = EVP_CIPHER_CTX_block_size(ctx);
    std::string cipher_buf;
    cipher_buf.resize(MAX_BUFFER_SIZE + blocksize);

    std::string buffer;
    buffer.resize(MAX_BUFFER_SIZE);

    EVP_CipherUpdate(ctx, reinterpret_cast<uchar *>(cipher_buf.data())
        , &out_len, reinterpret_cast<uchar *>(str.data()), str.length());
    buffer.append(cipher_buf.data(), out_len);

    EVP_CipherFinal(ctx, reinterpret_cast<uchar *>(cipher_buf.data()), &out_len);
    buffer.append(cipher_buf.data(), out_len);
    
    auto s = buffer.size(); 

    //std::string test = base64_encode(buffer.c_str(), buffer.length());
    //std::string test = base64_decode(buffer);
    EVP_CIPHER_CTX_free(ctx);

    return;
}


INT main(INT argc, PCHAR* argv)
{
    decryptOrEncrypt();
}

The problem with the code above is that after the latest buffer.append, buffer is empty (Checking the data on Visual Studio debugger), but its size auto s = buffer.size() is 31.

3
  • I'm actually not sure what you'd expect that code to do, it doesn't output anything. Can you clarify that, along with making sure it qualifies as a minimal reproducible example? Hint: That should make your bug obvious! BTW: A better type for buffers is std::vector, which you instantiate with the correct type in order to get rid of all the unnecessary type casts in your code. Commented Nov 20, 2022 at 18:54
  • @UlrichEckhardt isn't my code a reproducible example? "That should make your bug obvious" what bug do you mean? On my side, it compiles without any errors. Commented Nov 20, 2022 at 19:05
  • "I'm actually not sure what you'd expect that code to do, it doesn't output anything" I'm first trying to get the encryption/decryption working to finish the function. Commented Nov 20, 2022 at 19:09

1 Answer 1

1

Your main problem with your example is you misunderstanding of the string size of buffer.

After with this line:

   buffer.resize(MAX_BUFFER_SIZE);

buffer string size is MAX_BUFFER_SIZE with all the characters set to zero.

After this line:

buffer.append(cipher_buf.data(), out_len);

Your buffer string is MAX_BUFFER_SIZE + out_len. The data from cipher_buf is in buffer but start at the offset of MAX_BUFFER_SIZE.

So to quickly fix your problem you only need to remove the line:

   buffer.resize(MAX_BUFFER_SIZE);

The rest of your question about converting to base64 can be done in many differnt ways. OpenSll library supports at least two api's to convert to/from base64. The BIO layer api or the EVP api.

Here is a example of your code with full encrypt to base64 string / decrypt from base64 string. I don't like using std::string as a buffer so I used a std::vector instead. There is also basic error checking added as well.

#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/err.h>

#include <iostream>
#include <format>
#include <string>
#include <vector>
#include <memory>

//#define USE_BIO

using namespace std::string_literals;

template<typename T, typename D>
std::unique_ptr<T, D> make_handle(T* handle, D deleter)
{
    return std::unique_ptr<T, D>{handle, deleter};
}

void CheckResult(int const result)
{
    if(!result)
    {
        auto const errorText = std::format("{}", ERR_error_string(ERR_get_error(), nullptr));
        throw std::exception(errorText.c_str());
    }
}

std::string GetBase64String(std::vector<unsigned char> const& data)
{
#ifdef USE_BIO
    auto const out = make_handle(BIO_push(BIO_new(BIO_f_base64()), BIO_new(BIO_s_mem())), BIO_free_all);
    BIO_set_flags(out.get(), BIO_FLAGS_BASE64_NO_NL);
    BIO_set_close(out.get(), BIO_CLOSE);

    CheckResult(BIO_write(out.get(), data.data(), static_cast<int>(data.size())));
    CheckResult(BIO_flush(out.get()));

    // ensure null terminated
    char* p;
    auto const length = BIO_get_mem_data(out.get(), &p);
    CheckResult(length != -1);
    return std::string(p, length);
#else
    const auto expected_length = 4 * ((data.size() + 2) / 3);
    std::string rv;
    rv.resize(expected_length + 1); // account for the null character
    auto const out_length = EVP_EncodeBlock(reinterpret_cast<unsigned char*>(rv.data()), data.data(), static_cast<int>(data.size()));
    CheckResult(out_length != -1);
    rv.resize(out_length);
    return rv;
#endif
}

#ifndef USE_BIO
size_t GetBase64StringPassingSize(std::string const& base64Data)
{
    // returns the number of '=' at the end of the base64 string as this is the
    // padding size to the base64 block size
    auto const pos = base64Data.find_last_not_of('=');
    if(pos == std::string::npos)
    {
        return 0;
    }
    return base64Data.size() - pos - 1;
}
#endif

std::vector<unsigned char> GetDataFromBase64String(std::string const& base64Data)
{
#ifdef USE_BIO
    auto const out = make_handle(BIO_push(BIO_new(BIO_f_base64()), BIO_new_mem_buf(base64Data.data(), static_cast<int>(base64Data.size()))), BIO_free_all);
    BIO_set_flags(out.get(), BIO_FLAGS_BASE64_NO_NL);
    BIO_set_close(out.get(), BIO_CLOSE);

    std::vector<unsigned char> rv;
    rv.resize(base64Data.size());

    auto const out_length = BIO_read(out.get(), rv.data(), static_cast<int>(rv.size()));
    CheckResult(out_length != -1);
    rv.resize(out_length);

    return rv;
#else
    const auto excepted_length = 3 * base64Data.size() / 4;
    const auto padding_length = GetBase64StringPassingSize(base64Data);
    std::vector<unsigned char> rv;
    rv.resize(excepted_length);
    auto const out_length = EVP_DecodeBlock(rv.data(), reinterpret_cast<unsigned char const*>(base64Data.data()), static_cast<int>(base64Data.size()));
    CheckResult(out_length != -1);
    rv.resize(out_length - padding_length);
    return rv;
#endif
}

std::string Encrypt(std::string const& str, std::string const& key, std::string const& iv)
{
    auto const MAX_BUFFER_SIZE = str.length();

    auto const ctx = make_handle(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
    CheckResult(static_cast<bool>(ctx));

    CheckResult(EVP_CipherInit(ctx.get(), EVP_aes_128_cbc(), reinterpret_cast<unsigned char const*>(key.data()), reinterpret_cast<unsigned char const*>(iv.data()), true));
    const int block_size = EVP_CIPHER_CTX_block_size(ctx.get());

    std::vector<unsigned char> cipherBuffer;
    cipherBuffer.resize(MAX_BUFFER_SIZE + block_size);

    int out_length;
    CheckResult(EVP_CipherUpdate(ctx.get(), cipherBuffer.data(), &out_length, reinterpret_cast<unsigned char const*>(str.data()), static_cast<int>(str.length())));
    cipherBuffer.resize(out_length + MAX_BUFFER_SIZE + block_size);
    auto totalLength = out_length;

    CheckResult(EVP_CipherFinal(ctx.get(), cipherBuffer.data() + totalLength, &out_length));
    totalLength += out_length;
    cipherBuffer.resize(totalLength);

    auto encodedBase64String = GetBase64String(cipherBuffer);
    std::cout << "Encrypted base64 string: " << encodedBase64String << "\n";
    return encodedBase64String;
}

std::string Decrypt(std::string const& base64Encrypted, std::string const& key, std::string const& iv)
{

    auto const cipherBuffer = GetDataFromBase64String(base64Encrypted);
    auto const MAX_BUFFER_SIZE = cipherBuffer.size();

    auto const ctx = make_handle(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
    CheckResult(static_cast<bool>(ctx));

    CheckResult(EVP_CipherInit(ctx.get(), EVP_aes_128_cbc(), reinterpret_cast<unsigned char const*>(key.data()), reinterpret_cast<unsigned char const*>(iv.data()), false));
    const int block_size = EVP_CIPHER_CTX_block_size(ctx.get());

    std::string rv;
    rv.resize(MAX_BUFFER_SIZE + block_size);

    int out_length;
    CheckResult(EVP_CipherUpdate(ctx.get(), reinterpret_cast<unsigned char*>(rv.data()), &out_length, cipherBuffer.data(), static_cast<int>(cipherBuffer.size())));
    rv.resize(out_length + MAX_BUFFER_SIZE + block_size);
    auto totalLength = out_length;

    CheckResult(EVP_CipherFinal(ctx.get(), reinterpret_cast<unsigned char*>(rv.data()) + totalLength, &out_length));
    totalLength += out_length;
    rv.resize(totalLength);

    std::cout << "Decrypted string: " << rv << "\n";
    return rv;
}

bool RunEncryptDecryptTest()
{
    try
    {
        auto const key = "abcdabcdabcdabcd"s;
        auto const iv = "abcdabcdabcdabcd"s;
        auto const data = "testing testing"s;
        auto const encryptedData = Encrypt(data, key, iv);
        // encryptedData == C3Lx6DlB5m7bGn3ajCeo7g==
        auto const rv = Decrypt(encryptedData, key, iv);
        return data == rv;
    }
    catch(std::exception const& e)
    {
        std::cout << "Failed with: " << e.what() << "\n";
    }

    return false;
}
Sign up to request clarification or add additional context in comments.

4 Comments

What #define USE_BIO does and when i should use it?
It's just showing two ways of using Base64 convertion using openssl. It's up to you which one you like better. The two ways are using with BIO api (when USE_BIO is defined) or using EVP api (when USE_BIO is not defined).
I'm getting an error bad decryption, encrypting using JavaScript: pastebin.com/iSRfNH2Q and decrypting with your function
It was a problem with the key!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.