简体   繁体   中英

Encrypt and decrypt string using OpenSSL

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 .

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM