Datei hochladen
  1. base64hub
  2. Base64 im Code
  3. C++
Code

Base64 in C++

Die C++-Standardbibliothek hat kein Base64, auch nicht in C++23. Diese Seite zeigt eine kompakte Header-only-Implementierung, die wir per Fuzzing gegen Python getestet haben, und listet die APIs von Bibliotheken auf, die Sie vielleicht schon nutzen.

Eine Header-only-Implementierung (C++17)

encode nimmt das Alphabet und ein Padding-Flag entgegen, sodass dieselbe Funktion Standard- und URL-sicheres Base64 erzeugt. decode akzeptiert Eingaben mit und ohne Padding, lehnt aber ungültige Zeichen, falsch platzierte =, unmögliche Längen und Padding-Bits ungleich null ab.

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

Der Decoder arbeitet als Bit-Akkumulator: Jedes Zeichen fügt 6 Bit hinzu, und je volle 8 Bit ergeben ein Byte. Was am Ende übrig bleibt, sind die Padding-Bits, und die müssen null sein. Warum, zeigt die Seite So funktioniert Base64.

Strings kodieren und dekodieren

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

Ein std::string enthält Bytes. Mit einer UTF-8-Quelldatei (Standard bei GCC und Clang; MSVC braucht /utf-8) sind Literale UTF-8, und die Ausgabe stimmt mit jeder anderen Sprache überein. Für std::u8string in C++20 übergeben Sie s.data() und s.size() an die Zeiger-Überladung.

URL-sicher und ohne Padding

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 "<<???>>"

Die Escapes sind nötig, weil ??> in C++ vor C++17 ein Trigraph ist; GCC ersetzt ihn in den strikten Modi -std=c++11 / c++14. JWTs und die meisten Tokens verwenden Base64URL ohne Padding; siehe Base64 vs. Base64URL.

Ergebnisse der Prüfung

Was decode zurückgibt, alles verifiziert:

EingabeErgebnis
aGk=2 Bytes
aGk2 Bytes (ohne Padding akzeptiert)
aA== / aA1 Byte
aGl=nullopt (Padding-Bits ungleich null)
aGk==nullopt (falsche Länge)
a===nullopt (zu viel Padding)
aGk=!!nullopt
PDw_Pz8-Pg==nullopt mit kStd

Wir haben mit g++ -std=c++17 -Wall -Wextra -Wpedantic -fsanitize=address,undefined gebaut, 5.000 zufällige Puffer in beiden Alphabeten per Roundtrip geprüft, den Encoder mit Pythons b64encode verglichen und die Entscheidung „akzeptiert/abgelehnt“ für 30.000 zufällige Strings gegen eine Python-Referenz kontrolliert. Keine Abweichungen. Um einen einzelnen String von Hand zu prüfen, erklärt der Base64-Validator jede Ablehnung.

Dateien

#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 ist unter Windows wichtig, wo der Textmodus Zeilenenden umwandelt und Binärdaten beschädigt. Bei großen Dateien lesen Sie Blöcke, deren Größe ein Vielfaches von 3 ist, und rufen für jeden encode auf; die Ausgaben lassen sich sauber aneinanderhängen. Die Seite Base64 in C zeigt diese Schleife. Um das Ergebnis als C-Array einzubetten, erzeugt es das Tool Base64 in Byte-Array.

Bibliotheken, die Sie vielleicht schon haben

Ist eine davon bereits eine Abhängigkeit, spart Ihnen ihre Base64-API eine Datei:

BibliothekKodierenDekodieren
OpenSSLEVP_EncodeBlockEVP_DecodeBlock (gibt die Länge einschließlich Padding-Bytes zurück)
Abseilabsl::Base64Escape, absl::WebSafeBase64Escapeabsl::Base64Unescape, absl::WebSafeBase64Unescape
QtQByteArray::toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)QByteArray::fromBase64
PocoPoco::Base64Encoder (ein ostream)Poco::Base64Decoder (ein istream)
Boostboost::beast::detail::base64 (intern, keine stabile API)ebenso

Prüfen Sie, wie die jeweilige Bibliothek mit Padding und Leerraum umgeht, bevor Sie sich bei der Validierung darauf verlassen; sie unterscheiden sich. EVP_DecodeBlock zieht zum Beispiel das Padding nicht von seinem Rückgabewert ab, aGk= meldet also 3 Bytes statt 2.

Fallstricke

  • Vorzeichenbehaftetes char als Index. table[s[i]] ist für Bytes über 127 undefiniert, wenn char vorzeichenbehaftet ist. Casten Sie wie oben nach unsigned char.
  • Dekodierte Bytes als C-String behandeln. Dekodierte Bytes können \0 enthalten; reichen Sie sie nie an strlen oder an eine API weiter, die ein bloßes const char* ohne Länge erwartet.
  • Puffer mit n * 4 / 3 bemessen. Das ist um bis zu 3 Zeichen zu knapp. Die genaue Formel steht in der Anleitung zum Größenzuwachs.

Häufige Fragen

Hat C++ eine Standardfunktion für Base64?

Nein. Weder die Standardbibliothek noch ein bis C++23 übernommener Vorschlag enthält eine. Verwenden Sie eine kleine Implementierung wie die auf dieser Seite oder die API einer Bibliothek, von der Ihr Projekt ohnehin abhängt.

Soll decode einen std::string oder einen std::vector<uint8_t> zurückgeben?

Dekodiertes Base64 ist binär, und std::vector<std::uint8_t> sagt das ehrlich. Wenn Sie wissen, dass die Payload Text ist, bauen Sie aus den Iteratoren des Vektors einen std::string.

Warum std::optional statt Exceptions?

Ungültiges Base64 aus Benutzereingaben ist ein erwarteter Fall, kein Ausnahmefall. std::optional zwingt den Aufrufer, ihn zu behandeln, und kostet im Normalfall nichts. Bevorzugt Ihre Codebasis Exceptions, werfen Sie sie in einem Wrapper.

Weiterlesen