55

I'm trying to quickly encode a simple ASCII string to base64 (Basic HTTP Authentication using boost::asio) and not paste in any new code code or use any libraries beyond boost.

Simple signature would look like: string Base64Encode(const string& text);

Again I realize the algorithm is easy and there are many libraries/examples doing this but I'm looking for a clean boost example. I found boost serialization but no clear examples there or from Google. http://www.boost.org/doc/libs/1_46_1/libs/serialization/doc/dataflow.html

Is this possible without adding the actual base64 algorithm explicitly to my code?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
MattiasF
  • 1,273
  • 1
  • 12
  • 15
  • 2
    Please have a look at my example program in a similar question, which converts strings to and from base64 using boost and accounts for the correct padding (in contrast to the accepted answer): http://stackoverflow.com/a/10973348/1132850 – PiQuer Jun 11 '12 at 14:51
  • Late to the party! But this [Boost Beast file](https://github.com/boostorg/beast/blob/6f08814a0c291eb9f03aaa1daefffdc45c1b9087/include/boost/beast/core/detail/base64.hpp) had exactly what I needed. – Sergio Basurco Feb 16 '18 at 13:21

8 Answers8

49

Here is my solution. It uses the same basic technique as the other solutions on this page, but solves the problem of the padding in what I feel is a more elegant way. This solution also makes use of C++11.

I think that most of the code is self explanatory. The bit of math in the encode function calculates the number of '=' characters we need to add. The modulo 3 of val.size() the remainder, but what we really want is the difference between val.size() and the next number divisible by three. Since we have the remainder we can just subtract the remainder from 3, but that leaves 3 in the case that we want 0, so we have to modulo 3 one more time.

#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/algorithm/string.hpp>

std::string decode64(const std::string &val) {
    using namespace boost::archive::iterators;
    using It = transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
    return boost::algorithm::trim_right_copy_if(std::string(It(std::begin(val)), It(std::end(val))), [](char c) {
        return c == '\0';
    });
}

std::string encode64(const std::string &val) {
    using namespace boost::archive::iterators;
    using It = base64_from_binary<transform_width<std::string::const_iterator, 6, 8>>;
    auto tmp = std::string(It(std::begin(val)), It(std::end(val)));
    return tmp.append((3 - val.size() % 3) % 3, '=');
}
ltc
  • 3,313
  • 1
  • 28
  • 26
43

I improved the example in the link you provided a little:

#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/insert_linebreaks.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/archive/iterators/ostream_iterator.hpp>
#include <sstream>
#include <string>
#include <iostream>


int main()
{
    using namespace boost::archive::iterators;

    std::string test = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce ornare ullamcorper ipsum ac gravida.";

    std::stringstream os;
    typedef 
        insert_linebreaks<         // insert line breaks every 72 characters
            base64_from_binary<    // convert binary values to base64 characters
                transform_width<   // retrieve 6 bit integers from a sequence of 8 bit bytes
                    const char *,
                    6,
                    8
                >
            > 
            ,72
        > 
        base64_text; // compose all the above operations in to a new iterator

    std::copy(
        base64_text(test.c_str()),
        base64_text(test.c_str() + test.size()),
        ostream_iterator<char>(os)
    );

    std::cout << os.str();
}

This prints the string encoded base64 nicely formated with a line break every 72 characters onto the console, ready to be put into an email. If you don't like the linebreaks, just stay with this:

    typedef 
        base64_from_binary<
           transform_width<
                const char *,
                6,
                8
            >
        > 
        base64_text;
Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
Tobias Schlegel
  • 3,970
  • 18
  • 22
  • This is great! It should be split at 76 characters though. – DanDan Mar 30 '12 at 15:05
  • 3
    You also need to pad with 0 (before the transform) and '=' (after the transform) if the input data buffer is not a multiple of 3. – DanDan Apr 02 '12 at 12:48
  • 4
    how would you pad with the solution above? – Tobi Nov 19 '13 at 12:24
  • @DanDan The other solutions don't appear to prepend zeroes although they append '=' to the result. Is this right? – Lightness Races in Orbit Sep 16 '15 at 13:11
  • 1
    It is required- "When the number of bytes to encode is not divisible by three (that is, if there are only one or two bytes of input for the last 24-bit block), then the following action is performed: Add extra bytes with value zero so there are three bytes, and perform the conversion to base64." - wikipedia. – DanDan Sep 16 '15 at 14:15
  • I created a version based on this with the padding here: http://www.webbiscuit.co.uk/base64-encoder-and-boost/ - however I would not use this way now, as mentioned below the boost decoding is broken, or at least was when I last tried this. I'd probably use a dedicated lib for this now, such as http://www.cryptopp.com/ – DanDan Sep 16 '15 at 14:18
27

You could use beast's implementation.

For boost version 1.71, the functions are:

boost::beast::detail::base64::encode()
boost::beast::detail::base64::encoded_size()
boost::beast::detail::base64::decode()
boost::beast::detail::base64::decoded_size()

From #include <boost/beast/core/detail/base64.hpp>

For older versions back to beast's inclusion in 1.66, the functions are:

boost::beast::detail::base64_encode()
boost::beast::detail::base64_decode()

From #include <boost/beast/core/detail/base64.hpp>

Ben
  • 429
  • 4
  • 6
  • 11
    Declarations in "detail" namespaces are considered private, and should not be relied upon! – Vinnie Falco Jan 14 '18 at 12:45
  • 1
    Copy/pasting the code fixes the unreliable nature of the detail namespace. The interfase (data/len/in/out) is so much better than typical c++ interfaces. – Erik Aronesty Nov 21 '18 at 16:44
  • 1
    Per @VinnieFalco indeed the API has changed in boost 1.71. It is now `boost::beast::detail::base64::encode()` – alkalinity Sep 20 '19 at 15:02
  • @VinnieFalco you're right; I put in a stable interface request for these functions on beast's github. – Ben Sep 24 '19 at 18:21
  • @Ben do you have a link? Many people would like to lend support to that request – sehe Jul 22 '22 at 12:23
  • 1
    @sehe the feature request for a stable base64 in beast is here: https://github.com/boostorg/beast/issues/1710 – Ben Jul 28 '22 at 22:04
15

Another solution using boost base64 encode decode:

const std::string base64_padding[] = {"", "==","="};
std::string base64_encode(const std::string& s) {
  namespace bai = boost::archive::iterators;

  std::stringstream os;

  // convert binary values to base64 characters
  typedef bai::base64_from_binary
  // retrieve 6 bit integers from a sequence of 8 bit bytes
  <bai::transform_width<const char *, 6, 8> > base64_enc; // compose all the above operations in to a new iterator

  std::copy(base64_enc(s.c_str()), base64_enc(s.c_str() + s.size()),
            std::ostream_iterator<char>(os));

  os << base64_padding[s.size() % 3];
  return os.str();
}

std::string base64_decode(const std::string& s) {
  namespace bai = boost::archive::iterators;

  std::stringstream os;

  typedef bai::transform_width<bai::binary_from_base64<const char *>, 8, 6> base64_dec;

  unsigned int size = s.size();

  // Remove the padding characters, cf. https://svn.boost.org/trac/boost/ticket/5629
  if (size && s[size - 1] == '=') {
    --size;
    if (size && s[size - 1] == '=') --size;
  }
  if (size == 0) return std::string();

  std::copy(base64_dec(s.data()), base64_dec(s.data() + size),
            std::ostream_iterator<char>(os));

  return os.str();
}

And here are the test cases:

    std::string t_e[TESTSET_SIZE] = {
        ""
      , "M"
      , "Ma"
      , "Man"
      , "pleasure."
      , "leasure."
      , "easure."
      , "asure."
      , "sure."
};
std::string t_d[TESTSET_SIZE] = {
        ""
      , "TQ=="
      , "TWE="
      , "TWFu"
      , "cGxlYXN1cmUu"
      , "bGVhc3VyZS4="
      , "ZWFzdXJlLg=="
      , "YXN1cmUu"
      , "c3VyZS4="
};

Hope this helps

ArtemGr
  • 11,684
  • 3
  • 52
  • 85
flare
  • 344
  • 2
  • 6
  • Using the above base64_decode function, I got the following error: "terminate called after throwing an instance of boost::archive::iterators::dataflow_exception what(): attempt to decode a value not in base64 char set". However, I was able to resolve it. See my question thread: [attempt-to-decode-a-value-not-in-base64-char-set](http://stackoverflow.com/questions/34680998/attempt-to-decode-a-value-not-in-base64-char-set/34733928#34733928) – ap6491 Jan 12 '16 at 01:10
6

For anyone coming here from Google, here's my base64 encode/decode functions based off boost. It handles padding correctly as per DanDan's comment above. The decode functions stops when it encounters an illegal character, and returns a pointer to that character, which is great if you're parsing base64 in json or xml.

///
/// Convert up to len bytes of binary data in src to base64 and store it in dest
///
/// \param dest Destination buffer to hold the base64 data.
/// \param src Source binary data.
/// \param len The number of bytes of src to convert.
///
/// \return The number of characters written to dest.
/// \remarks Does not store a terminating null in dest.
///
uint base64_encode(char* dest, const char* src, uint len)
{
    char tail[3] = {0,0,0};
    typedef base64_from_binary<transform_width<const char *, 6, 8> > base64_enc;

    uint one_third_len = len/3;
    uint len_rounded_down = one_third_len*3;
    uint j = len_rounded_down + one_third_len;

    std::copy(base64_enc(src), base64_enc(src + len_rounded_down), dest);

    if (len_rounded_down != len)
    {
        uint i=0;
        for(; i < len - len_rounded_down; ++i)
        {
            tail[i] = src[len_rounded_down+i];
        }

        std::copy(base64_enc(tail), base64_enc(tail + 3), dest + j);

        for(i=len + one_third_len + 1; i < j+4; ++i)
        {
            dest[i] = '=';
        }

        return i;
    }

    return j;
}

///
/// Convert null-terminated string src from base64 to binary and store it in dest.
///
/// \param dest Destination buffer
/// \param src Source base64 string
/// \param len Pointer to unsigned int representing size of dest buffer. After function returns this is set to the number of character written to dest.
///
/// \return Pointer to first character in source that could not be converted (the terminating null on success)
///
const char* base64_decode(char* dest, const char* src, uint* len)
{
    uint output_len = *len;

    typedef transform_width<binary_from_base64<const char*>, 8, 6> base64_dec;

    uint i=0;
    try
    {
        base64_dec src_it(src);
        for(; i < output_len; ++i)
        {
            *dest++ = *src_it;
            ++src_it;
        }
    }
    catch(dataflow_exception&)
    {
    }

    *len = i;
    return src + (i+2)/3*4; // bytes in = bytes out / 3 rounded up * 4
}
Eloff
  • 20,828
  • 17
  • 83
  • 112
3

This is another answer:

#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>

std::string ToBase64(const std::vector<unsigned char>& binary)
{
    using namespace boost::archive::iterators;
    using It = base64_from_binary<transform_width<std::vector<unsigned char>::const_iterator, 6, 8>>;
    auto base64 = std::string(It(binary.begin()), It(binary.end()));
    // Add padding.
    return base64.append((3 - binary.size() % 3) % 3, '=');
}

std::vector<unsigned char> FromBase64(const std::string& base64)
{
    using namespace boost::archive::iterators;
    using It = transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
    auto binary = std::vector<unsigned char>(It(base64.begin()), It(base64.end()));
    // Remove padding.
    auto length = base64.size();
    if(binary.size() > 2 && base64[length - 1] == '=' && base64[length - 2] == '=')
    {
        binary.erase(binary.end() - 2, binary.end());
    }
    else if(binary.size() > 1 && base64[length - 1] == '=')
    {
        binary.erase(binary.end() - 1, binary.end());
    }
    return binary;
}
Amir Saniyan
  • 13,014
  • 20
  • 92
  • 137
2

Base64 encode text and data

const std::string base64_padding[] = {"", "==","="};

std::string base64EncodeText(std::string text) {
    using namespace boost::archive::iterators;
    typedef std::string::const_iterator iterator_type;
    typedef base64_from_binary<transform_width<iterator_type, 6, 8> > base64_enc;
    std::stringstream ss;
    std::copy(base64_enc(text.begin()), base64_enc(text.end()), ostream_iterator<char>(ss));
    ss << base64_padding[text.size() % 3];
    return ss.str();
}

std::string base64EncodeData(std::vector<uint8_t> data) {
    using namespace boost::archive::iterators;
    typedef std::vector<uint8_t>::const_iterator iterator_type;
    typedef base64_from_binary<transform_width<iterator_type, 6, 8> > base64_enc;
    std::stringstream ss;
    std::copy(base64_enc(data.begin()), base64_enc(data.end()), ostream_iterator<char>(ss));
    ss << base64_padding[data.size() % 3];
    return ss.str();
}
neoneye
  • 50,398
  • 25
  • 166
  • 151
0

I modified the Answer 8 because it's not functional on my platform.

const std::string base64_padding[] = {"", "==","="};
std::string *m_ArchiveData;

/// \brief  To Base64 string
bool Base64Encode(string* output) 
{  
    try
    {
        UInt32 iPadding_Mask = 0;
        typedef boost::archive::iterators::base64_from_binary
            <boost::archive::iterators::transform_width<const char *, 6, 8> > Base64EncodeIterator;  
        UInt32 len = m_ArchiveData->size();
        std::stringstream os;

        std::copy(Base64EncodeIterator(m_ArchiveData->c_str()), 
            Base64EncodeIterator(m_ArchiveData->c_str()+len), 
            std::ostream_iterator<char>(os));

        iPadding_Mask = m_ArchiveData->size() % 3;
        os << base64_padding[iPadding_Pask];

        *output = os.str();
        return output->empty() == false;  
    }
    catch (...)
    {
        PLOG_ERROR_DEV("unknown error happens");
        return false;
    }
}  

/// \brief  From Base64 string
bool mcsf_data_header_byte_stream_archive::Base64Decode(const std::string *input) 
{  
    try
    {
        std::stringstream os;
        bool bPaded = false;
        typedef boost::archive::iterators::transform_width<boost::archive::iterators::
            binary_from_base64<const char *>, 8, 6> Base64DecodeIterator;  

        UInt32 iLength = input->length();
        // Remove the padding characters, cf. https://svn.boost.org/trac/boost/ticket/5629
        if (iLength && (*input)[iLength-1] == '=') {
            bPaded = true;
            --iLength;
            if (iLength && (*input)[iLength - 1] == '=') 
            {
                --iLength;
            }
        }
        if (iLength == 0)
        {
            return false;
        }

        if(bPaded)
        {
            iLength --;
        }

        copy(Base64DecodeIterator(input->c_str()) ,
            Base64DecodeIterator(input->c_str()+iLength), 
            ostream_iterator<char>(os)); 

        *m_ArchiveData = os.str();
        return m_ArchiveData->empty() == false;
    }
    catch (...)
    {
        PLOG_ERROR_DEV("unknown error happens");
        return false;
    }
}  
dazedconfused
  • 1,312
  • 2
  • 19
  • 26