I\'ve been trying to figure out the openssl documentation for base64 decoding and encoding. I found some code snippets below
Improved TCS answer to remove macros/datastructures
unsigned char *encodeb64mem( unsigned char *data, int len, int *lenoutput )
{
// bio is simply a class that wraps BIO* and it free the BIO in the destructor.
BIO *b64 = BIO_new(BIO_f_base64()); // create BIO to perform base64
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO *mem = BIO_new(BIO_s_mem()); // create BIO that holds the result
// chain base64 with mem, so writing to b64 will encode base64 and write to mem.
BIO_push(b64, mem);
// write data
bool done = false;
int res = 0;
while(!done)
{
res = BIO_write(b64, data, len);
if(res <= 0) // if failed
{
if(BIO_should_retry(b64)){
continue;
}
else // encoding failed
{
/* Handle Error!!! */
}
}
else // success!
done = true;
}
BIO_flush(b64);
// get a pointer to mem's data
unsigned char* output;
*lenoutput = BIO_get_mem_data(mem, &output);
// assign data to output
//std::string s(dt, len2);
return output;
}
To write to file
int encodeb64(unsigned char* input, const char* filenm, int leni)
{
BIO *b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);
BIO *file = BIO_new_file(filenm, "w");
BIO *mem = BIO_new(BIO_f_buffer());
BIO_push(b64, mem);
BIO_push(mem, file);
// write data
bool done = false;
int res = 0;
while(!done)
{
res = BIO_write(b64, input, leni);
if(res <= 0) // if failed
{
if(BIO_should_retry(b64)){
continue;
}
else // encoding failed
{
/* Handle Error!!! */
}
}
else // success!
done = true;
}
BIO_flush(b64);
BIO_pop(b64);
BIO_free_all(b64);
return 0;
}
Base64 encoding from file to file. Many times due to file constraint we have read in chunks of data and do encoding. Below is the code.
int encodeb64FromFile(const char* input, const char* outputfilename)
{
BIO *b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);
int leni = 3*64;
unsigned char *data[3*64];
BIO *file = BIO_new_file(outputfilename, "w");
BIO *mem = BIO_new(BIO_f_buffer());
BIO_push(b64, mem);
BIO_push(mem, file);
FILE *fp = fopen(input, "rb");
while ((leni = fread(data, 1, sizeof data, fp)) > 0) {
// write data
bool done = false;
int res = 0;
while(!done)
{
res = BIO_write(b64, data, leni);
if(res <= 0) // if failed
{
if(BIO_should_retry(b64)){
continue;
}
else // encoding failed
{
/* Handle Error!!! */
}
}
else // success!
done = true;
}
}
BIO_flush(b64);
BIO_pop(b64);
BIO_free_all(b64);
fclose(fp);
return 0;
}
I like mtrw's use of EVP.
Below is my "modern C++" take on his answer without manual memory allocation (calloc
). It will take a std::string
but it can easily be overloaded to use raw bytes for example.
#include <openssl/evp.h>
#include <memory>
#include <stdexcept>
#include <vector>
auto EncodeBase64(const std::string& to_encode) -> std::string {
/// @sa https://www.openssl.org/docs/manmaster/man3/EVP_EncodeBlock.html
const auto predicted_len = 4 * ((to_encode.length() + 2) / 3); // predict output size
const auto output_buffer{std::make_unique<char[]>(predicted_len + 1)};
const std::vector<unsigned char> vec_chars{to_encode.begin(), to_encode.end()}; // convert to_encode into uchar container
const auto output_len = EVP_EncodeBlock(reinterpret_cast<unsigned char*>(output_buffer.get()), vec_chars.data(), static_cast<int>(vec_chars.size()));
if (predicted_len != static_cast<unsigned long>(output_len)) {
throw std::runtime_error("EncodeBase64 error");
}
return output_buffer.get();
}
auto DecodeBase64(const std::string& to_decode) -> std::string {
/// @sa https://www.openssl.org/docs/manmaster/man3/EVP_DecodeBlock.html
const auto predicted_len = 3 * to_decode.length() / 4; // predict output size
const auto output_buffer{std::make_unique<char[]>(predicted_len + 1)};
const std::vector<unsigned char> vec_chars{to_decode.begin(), to_decode.end()}; // convert to_decode into uchar container
const auto output_len = EVP_DecodeBlock(reinterpret_cast<unsigned char*>(output_buffer.get()), vec_chars.data(), static_cast<int>(vec_chars.size()));
if (predicted_len != static_cast<unsigned long>(output_len)) {
throw std::runtime_error("DecodeBase64 error");
}
return output_buffer.get();
}
There's probably a cleaner/better way of doing this (I'd like to get rid of reinterpret_cast
). You'll also definitely want a try/catch
block to deal with the potential exception.