Upload a file
  1. base64hub
  2. Base64 in Code
  3. C++
Code

Base64 in C++

The C++ standard library has no Base64, not even in C++23. This page gives a compact header-only implementation that we fuzzed against Python, and lists the APIs of libraries you may already have.

A header-only implementation (C++17)

encode takes the alphabet and a padding flag, so the same function produces standard and URL-safe Base64. decode accepts padded and unpadded input but rejects bad characters, misplaced =, impossible lengths and non-zero padding bits.

// base64.hpp
#pragma once
#include <array>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

namespace b64 {

inline constexpr std::string_view kStd =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
inline constexpr std::string_view kUrl =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

inline std::string encode(const void* data, std::size_t n,
                          std::string_view abc = kStd, bool pad = true) {
    auto p = static_cast<const std::uint8_t*>(data);
    std::string out;
    out.reserve((n + 2) / 3 * 4);
    std::size_t i = 0;
    for (; i + 2 < n; i += 3) {
        std::uint32_t v = p[i] << 16 | p[i + 1] << 8 | p[i + 2];
        out += abc[v >> 18 & 63];
        out += abc[v >> 12 & 63];
        out += abc[v >> 6 & 63];
        out += abc[v & 63];
    }
    if (i < n) {
        std::uint32_t v = p[i] << 16 | (i + 1 < n ? p[i + 1] << 8 : 0);
        out += abc[v >> 18 & 63];
        out += abc[v >> 12 & 63];
        if (i + 1 < n) out += abc[v >> 6 & 63];
        if (pad) out.append(i + 1 < n ? 1 : 2, '=');
    }
    return out;
}

inline std::string encode(std::string_view s, std::string_view abc = kStd,
                          bool pad = true) {
    return encode(s.data(), s.size(), abc, pad);
}

inline std::optional<std::vector<std::uint8_t>> decode(
        std::string_view s, std::string_view abc = kStd) {
    std::array<int, 256> val;
    val.fill(-1);
    for (int i = 0; i < 64; ++i) val[static_cast<unsigned char>(abc[i])] = i;

    std::size_t len = s.size();
    if (len % 4 == 0 && len > 0 && s[len - 1] == '=')
        len -= (s[len - 2] == '=') ? 2 : 1;
    if (len % 4 == 1) return std::nullopt;
    if (s.size() != len && s.size() % 4 != 0) return std::nullopt;

    std::vector<std::uint8_t> out;
    out.reserve(len * 3 / 4);
    std::uint32_t acc = 0;
    int bits = 0;
    for (std::size_t i = 0; i < len; ++i) {
        int v = val[static_cast<unsigned char>(s[i])];
        if (v < 0) return std::nullopt;
        acc = (acc << 6) | static_cast<std::uint32_t>(v);
        bits += 6;
        if (bits >= 8) {
            bits -= 8;
            out.push_back(static_cast<std::uint8_t>(acc >> bits));
            acc &= (1u << bits) - 1;
        }
    }
    if (acc != 0) return std::nullopt;  // leftover bits must be zero
    return out;
}

}  // namespace b64

The decoder works as a bit accumulator: each character adds 6 bits and every full 8 bits become a byte. Whatever is left at the end are the padding bits, and they must be zero. The how Base64 works page shows why.

Encode and decode strings

#include "base64.hpp"
#include <iostream>

int main() {
    std::string enc = b64::encode("héllo wörld");
    std::cout << enc << '\n';                         // aMOpbGxvIHfDtnJsZA==

    if (auto bytes = b64::decode(enc))
        std::cout << std::string(bytes->begin(), bytes->end()) << '\n';  // héllo wörld
}

A std::string holds bytes. With a UTF-8 source file (the default for GCC and Clang; MSVC needs /utf-8), literals are UTF-8 and the output matches every other language. For std::u8string in C++20, pass s.data() and s.size() to the pointer overload.

URL-safe and unpadded

std::string q = "<<?\?\?>>";                          // "<<???>>", escaped (see below)
b64::encode(q, b64::kUrl);                            // PDw_Pz8-Pg==
b64::encode(q, b64::kUrl, false);                     // PDw_Pz8-Pg
b64::decode("PDw_Pz8-Pg", b64::kUrl);                 // bytes of "<<???>>"

The escapes are there because ??> is a trigraph in C++ before C++17; GCC replaces it in strict -std=c++11 / c++14 modes. JWTs and most tokens use unpadded Base64URL; see Base64 vs Base64URL.

Validation results

What decode returns, all verified:

InputResult
aGk=2 bytes
aGk2 bytes (unpadded accepted)
aA== / aA1 byte
aGl=nullopt (non-zero padding bits)
aGk==nullopt (bad length)
a===nullopt (too much padding)
aGk=!!nullopt
PDw_Pz8-Pg==nullopt with kStd

We built with g++ -std=c++17 -Wall -Wextra -Wpedantic -fsanitize=address,undefined, round-tripped 5,000 random buffers in both alphabets, compared the encoder with Python’s b64encode, and checked the accept/reject decision for 30,000 random strings against a Python reference. No mismatches. To check a single string by hand, the Base64 validator explains each rejection.

Files

#include <fstream>
#include <iterator>

std::ifstream f("hello.txt", std::ios::binary);
std::string data((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
std::cout << b64::encode(data) << '\n';               // aGVsbG8K

auto raw = b64::decode("aGVsbG8K");
std::ofstream("copy.txt", std::ios::binary)
    .write(reinterpret_cast<const char*>(raw->data()),
           static_cast<std::streamsize>(raw->size()));

std::ios::binary matters on Windows, where text mode converts line endings and corrupts binary data. For large files, read blocks whose size is a multiple of 3 and call encode on each; the output concatenates cleanly. The Base64 in C page shows that loop. To turn the result into a C array for embedding, the Base64 to byte array tool generates it.

Libraries you may already have

If one of these is already a dependency, its Base64 API saves you a file:

LibraryEncodeDecode
OpenSSLEVP_EncodeBlockEVP_DecodeBlock (returns length including padding bytes)
Abseilabsl::Base64Escape, absl::WebSafeBase64Escapeabsl::Base64Unescape, absl::WebSafeBase64Unescape
QtQByteArray::toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)QByteArray::fromBase64
PocoPoco::Base64Encoder (an ostream)Poco::Base64Decoder (an istream)
Boostboost::beast::detail::base64 (internal, not a stable API)same

Check each library’s handling of padding and whitespace before you rely on it for validation; they differ. For example, EVP_DecodeBlock does not subtract padding from its return value, so aGk= reports 3 bytes, not 2.

Pitfalls

  • Signed char as an index. table[s[i]] is undefined for bytes above 127 when char is signed. Cast to unsigned char as above.
  • Treating decoded bytes as a C string. Decoded bytes may contain \0; never pass them through strlen or any API that takes a bare const char* without a length.
  • Sizing buffers as n * 4 / 3. It is short by up to 3 characters. The exact formula is in the size overhead guide.

FAQ

Does C++ have a standard Base64 function?

No. Neither the standard library nor any proposal merged up to C++23 includes one. Use a small implementation like the one on this page, or the API of a library your project already depends on.

Should decode return std::string or std::vector<uint8_t>?

Decoded Base64 is binary, so std::vector<std::uint8_t> states that honestly. If you know the payload is text, construct a std::string from the vector’s iterators.

Why use std::optional instead of exceptions?

Invalid Base64 from user input is an expected case, not an exceptional one. std::optional makes the caller handle it and costs nothing on the happy path. Throw from a wrapper if your codebase prefers exceptions.

Keep reading