如何只使用boost将字符串编码为base64?

56
我试图快速将简单的ASCII字符串编码为base64(使用boost :: asio进行基本HTTP身份验证),而不粘贴任何新代码或使用除boost之外的任何库。
简单的签名如下: string Base64Encode(const string& text); 再次强调,我知道算法很简单,有许多库/示例可以完成此操作,但我正在寻找一个干净的boost示例。我发现了boost序列化,但那里没有清晰的示例或来自Google的示例。 http://www.boost.org/doc/libs/1_46_1/libs/serialization/doc/dataflow.html 是否可以在不将实际的base64算法明确添加到我的代码中的情况下实现这一点?

2
请查看我在类似问题中的示例程序,该程序使用boost将字符串转换为base64并考虑正确的填充(与被接受的答案相比):https://dev59.com/dGkv5IYBdhLWcg3wiRao#10973348 - PiQuer
晚到派对了!但是这个Boost Beast文件正好有我需要的东西。 - Sergio Basurco
9个回答

49

这是我的解决方案。它使用了与本页面上其他解决方案相同的基本技术,但以我认为更加优雅的方式解决了填充问题。此解决方案还利用了C ++11。

我认为大部分代码都是自说明的。编码函数中的数学位计算 '=' 字符的数量。val.size() 取模3的余数,但我们真正想要的是将 val.size() 减去下一个可被三整除的数字。由于我们有了余数,所以我们只需从3中减去余数,但如果我们想要0,则会留下3,因此我们还需要再取模3一次。

#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, '=');
}

3
我喜欢这里使用 using 的方式! - Uli Köhler
1
如果使用 It 会导致编译错误,您可以将其更改为: typedef transform_width<binary_from_base64<std::string::const_iterator>, 8, 6> It; - g.d.d.c
13
警告:如果您的val字符串末尾包含多个'\0'(通常情况下不会出现),那么这段代码可能会移除过多的字符。 - zpon
这个解决方案确实是无效的。 - HappyCactus

43

我稍微改进了你提供链接中的示例:

#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();
}

这将以base64编码格式漂亮地格式化字符串,并在每72个字符处添加换行符,准备输出到控制台以便放入电子邮件中。如果您不喜欢换行符,请使用以下代码:

    typedef 
        base64_from_binary<
           transform_width<
                const char *,
                6,
                8
            >
        > 
        base64_text;

这很棒!但应该在76个字符处分割。 - DanDan
3
如果输入数据缓冲区的长度不是3的倍数,你需要在变换之前用0进行填充,在变换之后用'='进行填充。 - DanDan
4
你会如何在上述解决方案中进行填充? - Tobi
@DanDan 其他解决方案似乎没有在结果前添加零,而是在结果后添加“=”。这样做对吗? - Lightness Races in Orbit
1
要求“当要编码的字节数不可被三整除(也就是说,如果最后一个24位块只有一个或两个输入字节),那么执行以下操作:添加值为零的额外字节,使得总共有三个字节,并进行Base64转换。”- 维基百科。 - DanDan
我基于这个版本添加了填充,链接在这里:http://www.webbiscuit.co.uk/base64-encoder-and-boost/ - 然而,如下所述,boost解密是有问题的,至少在我上次尝试时是这样。现在我可能会使用专门的库,比如http://www.cryptopp.com/。 - DanDan

29

您可以使用Beast的实现。

对于Boost版本1.71,函数如下:

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

从#include <boost/beast/core/detail/base64.hpp>导入。

对于版本较旧的库,包括在1.66中的函数为:

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

来自#include <boost/beast/core/detail/base64.hpp>的内容。


11
在 "detail" 命名空间中声明的内容被视为私有,不应依赖于它们! - Vinnie Falco
1
复制粘贴代码可以修复细节命名空间的不可靠性。接口(数据/长度/输入/输出)比典型的C++接口更好。 - Erik Aronesty
1
根据@VinnieFalco的说法,Boost 1.71中的API已经发生了变化。现在是boost::beast::detail::base64::encode() - alkalinity
@VinnieFalco 你是对的;我在beast的github上提出了这些函数的稳定接口请求。 - Ben
@Ben,你有链接吗?很多人想支持那个请求。 - sehe
1
@sehe,关于Beast稳定的base64功能请求在这里:https://github.com/boostorg/beast/issues/1710 - Ben

15

另一个使用Boost Base64编码解码的解决方案:

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

以下是测试用例:

    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="
};
希望这可以帮助到您。

使用上述base64_decode函数时,我遇到了以下错误:"terminate called after throwing an instance of boost::archive::iterators::dataflow_exception what(): attempt to decode a value not in base64 char set"。 然而,我已经解决了。请查看我的问题帖子:attempt-to-decode-a-value-not-in-base64-char-set - ap6491

6

对于从Google来到这里的任何人,以下是我基于boost开发的base64编码/解码函数。它可以正确处理填充,如DanDan在上面的评论中所说。解码函数在遇到非法字符时停止,并返回指向该字符的指针,如果您正在解析json或xml中的base64,则非常有用。

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

5

3

这是另一个答案:

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

1
感谢填充/取消填充。 - mgueydan

2

将文本和数据进行Base64编码

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

0

我修改了答案8,因为它在我的平台上无法正常运行。

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

5
你能否提供你认为的第八个答案的链接?由于新答案和投票的影响,答案编号会经常更改。 - Uli Köhler
2
并解释其中何为“无法正常工作”,以及我的平台是什么。 - Lightness Races in Orbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接