Haben Sie mit dem Base64-Format zu tun? Dann ist diese Website genau das Richtige für Sie! Nutzen Sie unser superpraktisches Online-Tool, um Ihre Daten zu kodieren oder zu dekodieren.

Base64-Dekodierung in C++ (Cpp): Ein vollständiger Leitfaden

Sie haben den String. Ein langes Band aus Buchstaben und Ziffern, gelegentlich ein + oder /, vielleicht ein = oder zwei am Ende, und irgendwo in Ihrem Ticket, Vertrag oder in einer Datenbank-Spalte die Zusage, dass es Base64 ist. Jetzt brauchen Sie die Original-Bytes zurück, in C++, und zwar richtig. Die Startseite dieser Site geht im Detail durch das Format, also gibt es hier nur die kurze Version: Vier Alphabetzeichen tragen drei Bytes, ein =-Tail aus einem oder zwei Zeichen markiert, wo die echten Daten endeten, und die kodierte Form wird etwa 33 Prozent größer als das Original. Dekodieren ist die schrumpfende Richtung, also braucht ein Decoder nie mehr Speicher als das Payload, das er bereits hält. Das ist eine wirklich angenehme Eigenschaft und einer der leisen Freuden, in diese Richtung zu arbeiten.

Die größere Schlagzeile ist, dass C++ selbst Ihnen nicht ein einziges Zeichen dekodiert. Die Standardbibliothek hatte dreißig Jahre Zeit, eine base64-Funktion wachsen zu lassen, und hat sie alle auf andere Dinge verwendet, also bringt jedes C++-Programm seinen eigenen Decoder aus einer Bank mit drei sehr unterschiedlichen Persönlichkeiten mit, plus der Option, etwa vierzig Zeilen davon selbst zu schreiben. Das eine ist ein Arbeitstier, das das Internet seit den 1990ern trägt, das andere ein schweigsamer Typ, der mitten im Satz stoppt und kein Wort davon sagt, und das dritte ein Pedant, der bei einem einzelnen versehentlichen Leerzeichen eine Ausnahme wirft. Sobald Sie wissen, was jeder von ihnen verzeiht, was er ablehnt und was er still hinter Ihrem Rücken macht, hört Dekodieren auf, eine Quelle für Mysterien-Bugs zu sein. Öffnen wir also ein paar Pakete.

Die Werkzeugkiste: Vier Decoder, vier Temperamente

Hier ist die Lage auf einen Blick. Alle vier bewältigen das Standardalphabet; die Unterschiede liegen an den Rändern, und genau dort leben die Bugs.

Decoder Woher er kommt Fehlermodell Kuriosität zum Merken
OpenSSL EVP <openssl/evp.h>, link -lcrypto Gibt -1 bei kaputter Eingabe zurück Die One-Shot-Version füllt den Tail mit Nullen
Boost.Beast <boost/beast/core/detail/base64.hpp>, header-only Keine: Er stoppt einfach Kein Fehlerkanal in irgendeiner Form
Boost.Serialization-Iteratoren <boost/archive/iterators/binary_from_base64.hpp>, header-only Wirft dataflow_exception Behandelt = als echten Nullwert
Ihre eigenen vierzig Zeilen Nirgendwo: Sie sind Ihre Ihre Wahl, bis zur Byte-Position Sie besitzen jeden Edge Case für immer

Die Installation ist ein Paketname pro Distribution. Für OpenSSL: libssl-dev auf Debian und Ubuntu, openssl-devel auf Fedora und RHEL, openssl auf Arch und brew install openssl auf macOS. Für Boost, dessen aktuelle Version 1.92.0 von August 2026 ist, aus einem Projekt, das seit 1998 Bibliotheken baut: libboost-dev oder boost-devel. Beide Boost-Decoder unten sind header-only, also gibt es überhaupt nichts zu linken. Wenn Ihr Projekt auf CMake basiert, ist das komplette Setup drei Zeilen:

find_package(OpenSSL REQUIRED)
find_package(Boost REQUIRED)
target_link_libraries(my_app PRIVATE OpenSSL::Crypto)

Ein Versionshinweis vor dem Code, weil er verändert, was Ihr Decoder zurückgibt. OpenSSL 3.5, im April 2025 als Langzeit-Support-Linie veröffentlicht, hat einen echten Bug im Streaming-Decoder gefixt (mehr in einer Minute), und das neuere 4.0-Feature-Release von April 2026 hat den Fix geerbt. Wenn Ihr Build eine alte 3.0 oder 3.3 festpinnt, lesen Sie den Versionsabsatz im OpenSSL-Abschnitt unten, bevor Sie einer Tail-Länge vertrauen.

OpenSSL: Der Decoder, den Sie wahrscheinlich schon linken

Wenn Ihr C++-Programm TLS, Hashing oder Zertifikate anfasst, ist OpenSSL bereits im Binary, und seine EVP-Base64-Routinen sind die kampfgeprüftesten Decoder im Geschäft. Die One-Shot-Funktion ist ein einzelner Aufruf:

int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n);

Reichen Sie ihm einen Buffer mit Base64-Zeichen und seiner Länge, und er schreibt die dekodierten Bytes nach t. Er streicht führende Leerzeichen, streicht abschließende Leerzeichen, Zeilenumbrüche und Wagenrückläufe, und dann wendet er Regeln ohne Kompromisse an: kein interner Leerraum, und die gestrichene Länge muss ein Vielfaches von 4 sein. Jedes vier Eingabezeichen produzieren genau drei Ausgabe-Bytes, und hier ist der Teil, der die Leute überrascht: Padding-Zeichen werden zu sechs Null-Bits dekodiert, und die Manpage stellt ruhig fest, dass der Aufrufer dafür verantwortlich ist, das abschließende Padding zu berücksichtigen. Mit anderen Worten: Die Funktion macht die Arithmetik für Sie und hängt dann still bis zu zwei Bonus-Null-Bytes ans Ende. Der idiomatische C++-Wrapper versteckt die Buffer-Mathematik hinter einem std::string:

#include <cstddef>
#include <cstdio>
#include <string>
#include <vector>
#include <openssl/evp.h>

std::string openssl_decode(const std::string &b64) {
  std::vector<unsigned char> out(b64.size() * 3 / 4 + 4);
  int n = EVP_DecodeBlock(out.data(),
                          reinterpret_cast<const unsigned char *>(b64.data()),
                          static_cast<int>(b64.size()));
  if (n < 0) return {};
  size_t pads = 0;
  for (size_t i = b64.size(); i > 0 && b64[i - 1] == '='; --i) ++pads;
  return std::string(reinterpret_cast<const char *>(out.data()),
                     static_cast<size_t>(n) - pads);
}

int main() {
  std::string one = openssl_decode("TQ==");
  std::printf("%zu bytes: %02x\n", one.size(),
              static_cast<unsigned char>(one[0]));
  std::string word = openssl_decode("TWFuZQ==");
  std::printf("%zu bytes: %s\n", word.size(), word.c_str());
}

Führen Sie es aus, und die Nullauffüllung taucht genau dort auf, wo die Dokumentation sie versprochen hat: Das Dekodieren des vier Zeichen langen Strings TQ== liefert drei Bytes, den Buchstaben M plus zwei Nullen, bevor der Wrapper sie wegstreicht. Füttern Sie ihm TWFuZQ==, und Sie bekommen die sauberen vier Bytes von "Mane". Füttern Sie ihm ein Zeichen außerhalb des Alphabets, und Sie bekommen einen leeren String zurück, weil die Funktion mit -1 geantwortet hat. Beachten Sie, was std::string still in diesem Wrapper tut: Er führt die eigene Länge und enthält gerne Null-Bytes, sodass ein dekodiertes JPEG in Ihrem Text-Typ leben kann und verglichen, gehasht und herumgereicht werden kann. In C würden Sie sich über eine Längenvariable freuen wie über ein Wunder; hier funktioniert der String einfach.

Für Daten, die in Stücken ankommen, gibt Ihnen OpenSSL einen Kontext, in den Sie Chunks hineinfüttern und aus dem Sie Ergebnisse herausholen, und die drei Funktionen haben ein kompaktes Rückgabewert-Vokabular:

Aufruf Rückgabe Was es bedeutet
EVP_DecodeUpdate -1 Ungültiges Zeichen, oder ein Pad-Zeichen in der Mitte der Daten
EVP_DecodeUpdate 1 Weitere Eingabe wird erwartet
EVP_DecodeUpdate 0 Ende der Daten: Die letzte Gruppe trug Padding, oder die weiche Eingabe-Ende-Marke erschien
EVP_DecodeFinal 1 / -1 Stream endete sauber / verbleibende Zeichen waren kein Vielfaches von 4

Zwei Verhaltensweisen machen den Streaming-Decoder zum nachsichtigsten in der Werkzeugkiste. Er überspringt Leerzeichen, Tabs, Wagenrückläufe und Zeilenumbrüche überall im Stream, sodass ein MIME-E-Mail-Block mit 76-Zeichen-CRLF-Zeilen genau wie eine einzeilige String durchläuft, und er meldet die wahre Byte-Anzahl: Das gleiche TQ==, das die One-Shot-Funktion getäuscht hat, gibt Ihnen hier genau ein Byte, ohne Arithmetik. Er kaut die Eingabe in Chunks von bis zu 64 Base64-Zeichen, arbeitet von einem internen 80-Byte-Buffer und puffert alles, was nicht in eine Vierer-Gruppe passt, weshalb Sie ihn in beliebigen Stückgrößen füttern können. Der Wrapper:

#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
#include <openssl/evp.h>

std::string openssl_decode_stream(const std::string &b64) {
  EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
  EVP_DecodeInit(ctx);
  std::string out;
  std::vector<unsigned char> chunk(1024);
  int outl = 0;
  for (size_t pos = 0; pos < b64.size(); pos += chunk.size()) {
    size_t take = std::min(chunk.size(), b64.size() - pos);
    int ret = EVP_DecodeUpdate(ctx, chunk.data(), &outl,
                               reinterpret_cast<const unsigned char *>(b64.data()) + pos,
                               static_cast<int>(take));
    if (ret < 0) {
      EVP_ENCODE_CTX_free(ctx);
      return {};
    }
    out.append(reinterpret_cast<const char *>(chunk.data()), outl);
    if (ret == 0) break;
  }
  unsigned char tail[3];
  int tail_l = 0;
  int fin = EVP_DecodeFinal(ctx, tail, &tail_l);
  EVP_ENCODE_CTX_free(ctx);
  if (fin != 1) return {};
  out.append(reinterpret_cast<const char *>(tail), tail_l);
  return out;
}

Und jetzt die Versionsfußnote aus der Tabelle der Werkzeugkiste, weil es genau die Art von Sache ist, die ein "gelöstes" Ticket wieder öffnet: In jeder OpenSSL-Version vor 3.5 hatte der Streaming-Pfad dieselbe Nullauffüll-Gewohnheit wie der Block-Decoder. Es wurde im Februar 2025 als issue 26677 gemeldet; der Fix-Commit landete am 27. Februar 2025 auf master, und der zugehörige Pull-Request wurde am selben Tag geschlossen. Die offizielle Manpage dokumentiert es jetzt in ihrem Geschichts-Abschnitt: Ab OpenSSL 3.5 erzeugt EVP_DecodeUpdate die Anzahl Bytes, die die Dokumentation immer behauptet hat, und dekodiert Padding nicht mehr zu Null-Bits. Wenn Ihr Codebase ein altes OpenSSL festpinnt und Ihre Tail-Längen ein oder zwei Bytes daneben aussehen, ist das das Erste, was Sie prüfen sollten. Und es gibt eine Exzentrik, die aus der PEM-Ära geerbt wurde: Der Bindestrich - ist gar kein Alphabetzeichen - er ist eine weiche Eingabe-Ende-Marke. Wenn Ihr Stream eines davon nach einem Vielfachen von 4 gültigen Zeichen enthält, gibt der Decoder 0 zurück und bittet Sie, aufzuhören, weshalb ein base64url-String, der wirklich seine --Zeichen braucht, nicht einfach dekodiert wird - transcodieren Sie zuerst, im Abschnitt unten, und der Zeitreisende verschwindet.

Boost.Beast: Der schnelle Decoder, der nie meckert

Wenn Boost bereits im Projekt ist, liefert seine HTTP-Bibliothek einen Base64-Codec an der unwahrscheinlichen Adresse boost/beast/core/detail/base64.hpp. Der detail::-Namespace ist Boosts Art zu sagen "das ist unser internes Geschäft", und die Maintainer haben es abgelehnt, ihn zu einer öffentlichen API zu erklären. Alle benutzen ihn trotzdem, weil er klein, schnell und header-only ist: Definieren Sie BOOST_BEAST_HEADER_ONLY vor dem include, und es gibt nichts zu linken. Er ist nebenbei auch noch der Codec, den Boost.Beasts eigener WebSocket-Handshake für die Sec-WebSocket-Accept-Berechnung verwendet, also kaut er seit Jahren echten Verkehr.

Die Persönlichkeit der Dekodier-Funktion ist die Überraschung. Sie nimmt Ihren Ausgabe-Buffer, die Eingabe und ihre Länge und gibt ein Paar zurück: die Anzahl der geschriebenen Oktette und die Anzahl der gelesenen Zeichen. Sie stoppt beim ersten = und beim ersten ungültigen Zeichen - und in beiden Fällen, ohne es Ihnen zu sagen. Es gibt keinen Fehlercode, keine Ausnahme, kein Status-Flag. Ein beschädigtes Payload, ein zeilenumgebrochenes Payload und ein abgeschnittener Tail produzieren alle ein erfolgreiches Teil-Ergebnis:

Eingabe Dekodierte Bytes Gelesene Zeichen Woran er stoppte
"TWFuZQ==" 4 Bytes "Mane" 6 Stoppt beim ersten =
"TWF!ZQ==" 2 Bytes "Ma" 3 Stoppt bei !
"TWFu\nZQ==" 3 Bytes "Man" 4 Stoppt bei \n
"TWFuZQ" 4 Bytes "Mane" 6 Der Tail wurde abgeschnitten
"TQ==" 1 Byte "M" 2 Padding sauber verarbeitet

Lesen Sie diese Tabelle ein zweites Mal, weil sie das gesamte Threat-Model eines Decoders ist, der nie meckert: Er hat sein Bestes getan, er ist dort stehen geblieben, wo er stehen geblieben ist, und es ist an Ihnen, es zu bemerken. Die Überprüfung hat einen Haken: Bei gepaddeter Eingabe stoppt die "gelesene Zeichen"-Zählung beim ersten =, also addieren Sie die Pads vor dem Vergleich wieder hinzu, und Sie verlangen außerdem, dass die Summe ein Vielfaches von vier ist, weil das die einzige Form ist, die ein echtes Payload hat:

#define BOOST_BEAST_HEADER_ONLY
#include <boost/beast/core/detail/base64.hpp>
#include <cstddef>
#include <string>

namespace b64 = boost::beast::detail::base64;

std::string beast_decode(const std::string &in) {
  std::string out;
  out.resize(in.size() / 4 * 3 + 3); /* Spielraum für ungerade Längen */
  auto result = b64::decode(out.data(), in.data(), in.size());
  out.resize(result.first);
  size_t pads = 0;
  for (size_t i = in.size(); i > 0 && in[i - 1] == '='; --i) ++pads;
  if (result.second + pads != in.size() || in.size() % 4 != 0)
    return {}; /* es ist zu früh gestoppt, oder der Tail war unmöglich */
  return out;
}

Zwei Details zum Abheften. Erstens: Die decoded_size(n)-Hilfsfunktion, auf die der Header Sie verweist, ist nur eine gültige Obergrenze, wenn n ein Vielfaches von 4 ist - der eigene Kommentar der Funktion sagt genau das - deshalb fügt der Wrapper oben ein paar Bytes Spielraum hinzu, statt ihr für beliebige Längen zu vertrauen. Zweitens, die Herkunft: Die Quelldateien tragen ein Urheberrecht 2016-2019 von Vinnie Falco, mit einem Footer, der Teile einem Snippet von Rene Nyffenegger aus 2004-2008 zuschreibt. Dieses Snippet ist das Base64-Paar, das seit zwei Jahrzehnten kopiert und eingefügt wird quer durch das englischsprachige Internet, und es wird jetzt in Boost ausgeliefert, in Ihrem Binary, und macht HTTP-Basic-Auth für das ganze Web.

Boost.Serialization: Der Decoder, der bei einem einzelnen Leerzeichen wirft

Boosts Serialisierungsbibliothek trägt das älteste Base64 im C++-Ökosystem: eine Sammlung kombinierbarer Iterator-Adapter aus dem Jahr 2002, verfasst von Robert Ramey, die "sei großzügig bei dem, was du annimmst" als persönliche Beleidigung auffassen. Die Dekodier-Richtung lebt in binary_from_base64.hpp (ja, der Name ist aus Sicht der Ausgabe) und wird mit einem Width-Transformer kombiniert, der Sechs-Bit-Werte in Acht-Bit-Bytes neu packt:

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

namespace it = boost::archive::iterators;

std::string boost_decode(const std::string &in) {
  using dec =
      it::transform_width<it::binary_from_base64<const char *>, 8, 6>;
  std::string out(dec(in.data()), dec(in.data() + in.size()));
  size_t pads = 0;
  for (size_t i = in.size(); i > 0 && in[i - 1] == '='; --i) ++pads;
  out.resize(out.size() - pads);
  return out;
}

Der innere Iterator wandelt jedes Base64-Zeichen in seinen Sechs-Bit-Wert um, und der äußere gruppiert diese Werte zu Bytes neu. Seine Strenge ist total: Jedes Zeichen außerhalb des Alphabets - einschließlich eines einzelnen Leerzeichens - lässt den Iterator boost::archive::iterators::dataflow_exception mit der Meldung "attempt to decode a value not in base64 char set" werfen. Das ist das Verhalten "ablehnen, es sei denn, es wird ausdrücklich anderes gesagt", das die RFCs später explizit formuliert haben - implementiert 2002, ein volles Jahr, bevor RFC 3548 dieselbe Regel kodifizierte. Die praktische Konsequenz ist, dass MIME-umgebrochene Eingabe ihrer Zeilenumbrüche beraubt werden muss, bevor sie diesen Iterator berührt. Die zweite Kuriosität ist subtiler: In der Lookup-Tabelle wird das Padding-Zeichen = nicht übersprungen - es wird als der Wert null gerendert. Das Dekodieren von TWFuZQ== erzeugt daher sechs Bytes - 4d 61 6e 65 00 00 - weil beide Pad-Zeichen echte (Null-)Daten beisteuerten, und die resize(size - pads)-Zeile im Snippet ist tragend, nicht dekorativ. Dekodieren Sie TQ==, und Sie bekommen drei Bytes, die sich auf den einzelnen Buchstaben M herunterstreichen, genau dort, wo Sie landen wollen.

Vierzig Zeilen, die Ihnen gehören

Base64 ist klein genug, dass ein korrekter Decoder etwas Respektables ist, das man besitzen kann, und in C++ ist der Ertrag besser als in jeder anderen Sprache: std::string macht das Buffer-Management angenehm, und ein selbstgebauter Decoder kann etwas, das keine der Bibliotheks-Versionen oben kann, nämlich auf das exakte Byte zeigen, das wehgetan hat. Diese Version folgt der strengen Lesart von RFC 4648 - die Enden streichen, internen Leerraum ablehnen, Padding in der Mitte ablehnen, die Längenregeln durchsetzen und sogar die Pad-Bits prüfen, die laut RFC ein konformer Encoder auf null gesetzt haben muss:

#include <cstddef>
#include <cstring>
#include <string>

std::string strict_decode(const std::string &in, size_t *error_pos = nullptr) {
  static const char *table =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  auto fail = [&](size_t pos) {
    if (error_pos) *error_pos = pos;
    return std::string();
  };
  size_t start = 0;
  size_t end = in.size();
  while (start < end && (in[start] == ' ' || in[start] == '\t' ||
                         in[start] == '\r' || in[start] == '\n'))
    ++start;
  while (end > start && (in[end - 1] == ' ' || in[end - 1] == '\t' ||
                         in[end - 1] == '\r' || in[end - 1] == '\n'))
    --end;
  size_t pads = 0;
  while (end > start && in[end - 1] == '=') {
    --end;
    ++pads;
  }
  size_t body = end - start;
  if (pads > 2 || (pads == 1 && body % 4 != 3) ||
      (pads == 2 && body % 4 != 2) || (pads == 0 && body % 4 == 1))
    return fail(in.size());
  if (body >= 2 && body % 4 != 0) {
    int leftover = static_cast<int>((body % 4) * 6 % 8);
    int last = static_cast<int>(std::strchr(table, in[end - 1]) - table);
    if ((last & ((1 << leftover) - 1)) != 0)
      return fail(end - 1); /* nicht-kanonische Pad-Bits */
  }
  int value = 0;
  int bits = -8;
  std::string out;
  out.reserve(body / 4 * 3);
  for (size_t i = start; i < end; ++i) {
    const char *p = std::strchr(table, in[i]);
    if (!p)
      return fail(i);
    value = (value << 6) + static_cast<int>(p - table);
    bits += 6;
    if (bits >= 0) {
      out.push_back(static_cast<char>((value >> bits) & 0xFF));
      bits -= 8;
    }
  }
  return out;
}

Gehen wir durch, was es durchsetzt. Führender und abschließender Leerraum wird gestrichen, weil ein Payload, das aus einem E-Mail-Header kopiert wurde, sehr wahrscheinlich eines davon trägt. Interner Leerraum wird abgelehnt, weil RFC 4648 sagt, ein Decoder MUST Nicht-Alphabet-Zeichen ablehnen, es sei denn, die umgebende Spezifikation sagt anderes, und an einer Sicherheitsgrenze wollen Sie die strenge Lesart. Ein Pad-Zeichen in der Mitte der Daten wird abgelehnt, ebenso wie jede Länge, die nicht zu einem echten Payload passen kann: ein Zeichen zu wenig in einer Gruppe ist unmöglich, und ein einzelnes Pad ist nur legal hinter drei Körper-Zeichen. Der Kanonizitäts-Check am Ende ist der, den die meisten Implementierungen überspringen: Wenn die letzte Gruppe ein oder zwei Pad-Zeichen hatte, müssen die ungenutzten unteren Bits des letzten Alphabetzeichens null sein, sonst könnten dieselben Bytes als zwei sichtbar verschiedene Strings geschrieben werden. Diese Formbarkeit ist der Grund, warum der Check existiert, und er kostet vier Zeilen. Außerdem akzeptiert der Decoder ungepaddete Eingabe, was genau das ist, was JWT-Segmente sind. Und bei einem Fehlschlag gibt er Ihnen die Position: Füttern Sie ihm TWF!ZQ==, und der Fehler sitzt an Index 3, auf dem Ausrufezeichen, der Unterschied zwischen einem Bug-Report und einem Fix.

Base64url: Das Alphabet, das Tokens sprechen

Das Standardalphabet hat zwei Zeichen, die eine URL nicht überleben: + bedeutet Leerzeichen in einem Query-String, und / bedeutet Verzeichnis in einem Pfad. RFC 4648, Abschnitt 5, behebt das mit zwei Zeichen-Tauschen - + wird - und / wird _ - und ist unmissverständlich über das Ergebnis: Diese Kodierung "sollte nicht als dieselbe wie die base64-Kodierung betrachtet werden". Es ist das Alphabet der JWTs, der OAuth-PKCE-Code-Challenges, der YouTube-Video-Identifikatoren und der meisten API-Tokens, und es streicht auch routinemäßig das =-Padding, weil in einem Token die Länge implizit bekannt ist und das Padding nur ein percent-Escape wäre, das darauf wartet, zu passieren. Keiner der C++-Decoder oben spricht es nativ - OpenSSL behandelt sogar ein - als diese weiche Eingabe-Ende-Marke - also ist der Fix ein kleines Transcoding, bevor Sie dekodieren. Es ist so kurz, dass es sich leicht im Kopf behalten lässt:

#include <string>

std::string url_to_standard(std::string in) {
  for (char &c : in) {
    if (c == '-') c = '+';
    else if (c == '_') c = '/';
  }
  switch (in.size() % 4) {
    case 2: in += "=="; break; /* das gestrichene Padding wiederherstellen */
    case 3: in += '=';  break;
    default: break;
  }
  return in;
}

Wenden Sie es auf ein echtes JWT an, und die ersten beiden Segmente sind plain JSON, das nur darauf wartet. Ein klassisches Beispiel-Token dekodiert zu einem Header {"alg":"HS256","typ":"JWT"} und einem Satz an Claims, die ein subject, einen name und einen issued-at-Zeitstempel enthalten. Das dritte Segment dekodiert auf dieselbe Weise und gibt Ihnen die rohen Signatur-Bytes - keinen Text, und keinen Beweis für irgendetwas. Das Dekodieren eines Tokens sagt Ihnen, was es behauptet; das Verifizieren der Signatur sagt Ihnen, ob Sie es glauben sollen, und das ist ein Kryptografie-Job, den keine base64-Bibliothek für Sie macht. Auf Windows ist die Lage in dieselbe Richtung einseitig: CryptBinaryToStringA hat ein CRYPT_STRING_BASE64URI-Flag für die Kodierungsseite, aber die Dekodier-Richtung hat gar kein URL-safe-Flag, also verdient das Transcoding oben seinen Platz in Ihrem Muskelgedächtnis auf jeder Plattform.

Von Bytes zurück zu Text

Fragen Sie einen C++-Decoder "welchen Zeichensatz habe ich gerade dekodiert?", und Sie bekommen die ehrlichste Antwort, die die Sprache hat: keinen. Decoder sind von Anfang bis Ende byte-orientiert. Sie sehen keinen Text; sie sehen Bytes, und sie geben Ihnen genau die Bytes zurück, die gepackt waren. Wenn das Original UTF-8 war, halten Sie jetzt UTF-8, und es wird nichts weiter benötigt. Der schöne C++-Twist ist, dass std::string selbst ein Byte-Container mit einem Längen-Mitglied ist, sodass der klassische C-Fehlermodus - eine String-Funktion, die beim ersten NUL stoppt - größtenteils verdampft. Eine dekodierte Datei, ein dekodiertes Zertifikat, ein dekodiertes Bild: Sie alle können in einem string leben, mit == verglichen, gehasht und per Wert übergeben werden, und die Null-Bytes drinnen sind einfach nur Bytes. Sie müssen nur nicht zu einem C-String konvertieren und ihn dann mit strlen vermessen; verwenden Sie size().

Für die Legacy-Kodierungen, die immer noch in alten Datenbanken, Exporten und handgeschriebenen Werkzeugen lauern - ISO-8859-1, Windows-1252 und ihre Verwandten - ist das Standardwerkzeug POSIX iconv, das mit glibc ausgeliefert wird. Dekodieren Sie zuerst auf Bytes, und konvertieren Sie dann diese Bytes mit dem Codec, der zur Quelle passt, in UTF-8:

#include <iconv.h>
#include <cstddef>
#include <string>
#include <vector>

std::string to_utf8(const std::vector<unsigned char> &raw,
                    const char *source_charset) {
  iconv_t cd = iconv_open("UTF-8", source_charset);
  if (cd == (iconv_t)-1) return {};
  char *inptr = reinterpret_cast<char *>(const_cast<unsigned char *>(raw.data()));
  size_t inleft = raw.size();
  std::vector<char> utf8buf(raw.size() * 4 + 8);
  char *outptr = utf8buf.data();
  size_t outleft = utf8buf.size();
  if (iconv(cd, &inptr, &inleft, &outptr, &outleft) == (size_t)-1) {
    iconv_close(cd);
    return {};
  }
  iconv_close(cd);
  return std::string(utf8buf.data(),
                     static_cast<size_t>(outptr - utf8buf.data()));
}

Die Rundreise ist in beiden Richtungen verlustfrei: Packen Sie einen String als ISO-8859-1, base64-en Sie ihn, versenden Sie ihn, dekodieren Sie ihn, konvertieren Sie ihn, und Sie bekommen genau das, womit Sie angefangen haben, mit den diakritischen Zeichen intakt. Und binäre Daten haben gar keinen Zeichensatz - ein PNG ist ein PNG, ob Sie es mögen oder nicht, und das ist die befreieste Antwort im gesamten Artikel.

Dateien dekodieren

Kleine Dateien sind ein Vier-Schritte-Tanz: binär öffnen, in einen vector lesen, dekodieren, das Ergebnis binär zurückschreiben. Binärmodus, jedes Mal, auf jeder Plattform - auf Windows würde ein Read im Textmodus CRLF-Paare in einzelne Zeilenumbrüche übersetzen und still Ihre Daten verändern, bevor der Decoder sie überhaupt sieht:

#include <fstream>
#include <iterator>
#include <string>
#include <vector>

std::vector<unsigned char> read_file(const std::string &path) {
  std::ifstream in(path, std::ios::binary);
  return {std::istreambuf_iterator<char>(in),
          std::istreambuf_iterator<char>()};
}

Beachten Sie die geschweiften Klammern im Code oben. Mit einfachen runden Klammern ist eine Zeile der Form std::vector<unsigned char> bytes(istreambuf_iterator<char>(file), istreambuf_iterator<char>()) der berüchtigte "most vexing parse": Der Compiler liest sie als Deklaration einer Funktion, die einen vector zurückgibt, und tut dabei absolut richtig. Die geschweifte Initialisierungsform oben umgeht die Grammatik komplett. Sobald die Bytes in der Hand sind, schicken Sie sie durch jeden Decoder aus diesem Artikel und schreiben Sie das Ergebnis mit std::ofstream in std::ios::binary, wobei Sie write(data.data(), data.size()) statt des Stream-Operators verwenden, damit eingebettete Null-Bytes die Reise auf die Platte überstehen. Eine .b64-Datei und ihr dekodiertes Zwillingstück unterscheiden sich dann um genau die 33-Prozent-Steuer, die Sie beim Hineinkommen gezahlt haben, was einen befriedigenden Checksummen-Moment ergibt.

Große Dateien: Streamen in beide Richtungen

Für Dateien, die zu groß sind, um sie im Speicher zu halten, erledigt der Streaming-Decoder aus dem OpenSSL-Abschnitt den ganzen Job: einen Chunk lesen, durch den Kontext schieben, das, was herauskommt, schreiben, wiederholen. Zu jedem Zeitpunkt lebt nur ein kleiner Buffer im RAM, also dekodiert eine 10-GB-Base64-Datei mit demselben Code wie eine 10-kB-Datei, und MIME-artiger Zeilenumbruch braucht auf dem Durchweg keine Vorverarbeitung, weil der Streaming-Decoder auf Zeilenumbrüche die Achtern zuckt:

#include <fstream>
#include <string>
#include <vector>
#include <openssl/evp.h>

bool decode_stream_to_file(const std::string &in_path,
                           const std::string &out_path) {
  std::ifstream in(in_path, std::ios::binary);
  std::ofstream out(out_path, std::ios::binary);
  if (!in || !out) return false;
  EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
  EVP_DecodeInit(ctx);
  std::string chunk(65536, '\0');
  std::vector<unsigned char> decoded(49152);
  bool ok = true;
  for (;;) {
    std::streamsize got = in.read(chunk.data(), chunk.size()).gcount();
    if (got < 0) { ok = false; break; }
    if (got == 0) break;
    int outl = 0;
    int ret = EVP_DecodeUpdate(ctx, decoded.data(), &outl,
                               reinterpret_cast<const unsigned char *>(chunk.data()),
                               static_cast<int>(got));
    if (ret < 0) { ok = false; break; }
    out.write(reinterpret_cast<const char *>(decoded.data()), outl);
    if (ret == 0) break;
  }
  unsigned char tail[3];
  int tail_l = 0;
  if (ok && EVP_DecodeFinal(ctx, tail, &tail_l) != 1)
    ok = false;
  if (ok)
    out.write(reinterpret_cast<const char *>(tail), tail_l);
  EVP_ENCODE_CTX_free(ctx);
  return ok;
}

Die Buffer-Größen sind nicht willkürlich: Die Längenparameter der EVP-Funktionen sind int, also ist ein einzelner Aufruf sicher bis 2 GB, und die Zahlen oben halten jeden Chunk bei 64 KB Eingabe mit einem 48-kB-Ausgabe-Buffer, was genau 3 von 4 ist. Diese int-Obergrenze ist der ganze Grund, warum der Streaming-Pfad existiert, und es lohnt sich, sie als harte Tatsache zu kennen, statt sie als Plattform-Bug zu entdecken. Wenn sich die Eingabe jemals als korrupt herausstellt, gibt die Funktion beim ersten Chunk, der nicht dekodiert werden kann, false zurück, und die Ausgabedatei hält alles, was davor gültig war - was, je nach Ihrer Pipeline, genau das Teil-Ergebnis sein kann, das Sie wollten.

HTTP, APIs und die JSON-Felder, die Bytes verstecken

Base64 taucht in HTTP in zwei Formen auf. Die erste ist Daten: eine JSON-Antwort mit einem "certificate"- oder "avatar"-Feld, voll mit base64, ein Upload-Endpoint, der die Bytes in einer text-sicheren Spalte akzeptiert, ein Download-Endpoint, der Ihnen eine .b64-Datei händigt. Das Muster ist immer dasselbe - das JSON parsen, den String herausziehen, dekodieren, das Ergebnis als Bytes behandeln - und die Dekodier-Seite dieses Artikels ist die komplette Implementierung. Die zweite Form sind Zugangsdaten: Der Authorization: Basic-Header ist das base64 von user:password, und er ist seit dreißig Jahren der eine Base64-Fall des Standards. Das Parsen ist zwei Schritte, und der erste ist der, an dem die Leute nach einem NUL-terminierten C-String in der Mitte von binär-nahen Daten greifen und sich wundern, warum:

#include <optional>
#include <string>

/* strict_decode aus dem Abschnitt "Vierzig Zeilen, die Ihnen gehören" */

std::optional<std::pair<std::string, std::string>> parse_basic_auth(
    const std::string &b64) {
  std::string raw = strict_decode(b64);
  size_t colon = raw.find(':');
  if (colon == std::string::npos)
    return std::nullopt;
  return std::make_pair(raw.substr(0, colon), raw.substr(colon + 1));
}

Reichen Sie ihm den Header-Wert hinter dem Basic -Präfix, und er gibt Ihnen den user und das password als ordentliche Strings mit geführter Länge zurück, oder garnichts, wenn das Payload kein user:pass-Paar ist. Der Sicherheitshinweis gehört hierher, auch wenn er kein C++-Thema ist: Basic-Auth ist Verdeckung, kein Schutz. Der Header reist im Klartext für jeden, der das Netzwerk lesen kann, also ist er nur hinter TLS akzeptabel, und selbst dann ist er die Wahl für Maschinen-auf-Maschinen-Aufrufe, nicht für Menschen.

JWTs: Lesen, was ein Token behauptet

Ein JSON Web Token sind drei base64url-Segmente, die mit Punkten verklebt sind: header, claims, signature. Die ersten beiden sind JSON-Objekte; das dritte ist eine kryptografische Signatur über den String header.claims, berechnet mit dem Algorithmus, der im header benannt ist. C++ hat keinen eingebauten JWT-Typ, aber zum Lesen eines Tokens braucht man nichts weiter als das Transcoding aus dem base64url-Abschnitt und einen Decoder, weil der interessante Teil das Lesen ist:

#include <cstddef>
#include <cstdio>
#include <string>

/* url_to_standard und strict_decode aus früheren Abschnitten */

int main() {
  const std::string token =
      "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
      "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
      "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
  size_t dot1 = token.find('.');
  size_t dot2 = token.find('.', dot1 + 1);
  std::string header = strict_decode(
      url_to_standard(token.substr(0, dot1)));
  std::string claims = strict_decode(
      url_to_standard(token.substr(dot1 + 1, dot2 - dot1 - 1)));
  std::printf("header: %s\n", header.c_str());
  std::printf("claims: %s\n", claims.c_str());
}

Der header kommt zurück als {"alg":"HS256","typ":"JWT"} und die claims als {"sub":"1234567890","name":"John Doe","iat":1516239022} - das subject, der name und ein issued-at-Zeitstempel. Das ist die gesamte Lese-Seite, und sie ist wirklich nützlich: Aufzeichnen, was ein Token behauptet, Debuggen eines 401 durch einen Blick auf das expiry-Feld oder die Entscheidung, welchen claims man vertraut, ist alles nur ein Dekodieren entfernt. Was es nicht ist, ist Verifizierung. Das Signatur-Segment ist auch base64url, und das Dekodieren ergibt 32 oder 64 rohe Bytes, die für sich allein nichts beweisen; die Signatur ist nur bedeutsam, wenn Sie den Hash von header.claims mit dem geteilten Geheimnis oder öffentlichen Schlüssel neu berechnen und vergleichen. Behandeln Sie ein dekodiertes JWT so, wie Sie einen Brief behandeln: Es sagt, was es sagt, und das Verifizieren des Siegels ist ein separater, kryptografischer Job.

Data-URIs: Dateien, die sich selbst in eine Seite kopiert haben

Eine Data-URI ist eine URL, deren Payload direkt in der Adresse steckt: data:, gefolgt von einem optionalen Medientyp, einem optionalen ;base64-Marker, einem Komma und dann den Daten selbst - das ganze Schema von RFC 2397. Browser nutzen sie, um Bilder, Schriftarten und kleine Skripte ohne zusätzliche Anfrage direkt in HTML und CSS einzubetten, und wenn Sie jemals eine Seite sehen, die mit deaktiviertem Netzwerk weiterarbeitet, ist eine Data-URI ein starker Verdächtiger. Auf der C++-Seite ist der Dekodier-Job, die URI aufzuspalten und dann das Payload durch Ihren üblichen Decoder zu jagen, denn wenn der ;base64-Marker vorhanden ist, ist das Payload plain Standard-base64 - normalerweise gepaddet, normalerweise auf einer Zeile:

#include <cstddef>
#include <string>

std::string data_uri_payload(const std::string &uri, bool *is_base64) {
  const std::string prefix = "data:";
  if (uri.rfind(prefix, 0) != 0)
    return {};
  size_t comma = uri.find(',');
  if (comma == std::string::npos)
    return {};
  std::string meta = uri.substr(prefix.size(), comma - prefix.size());
  *is_base64 = meta.size() >= 7 &&
               meta.compare(meta.size() - 7, 7, ";base64") == 0;
  return uri.substr(comma + 1);
}

Rufen Sie es mit data:image/png;base64,iVBORw0KGgo=... auf, und es gibt Ihnen das Payload plus den Flag zurück, der Ihnen sagt, welchen Dekodier-Pfad Sie nehmen sollen. Zwei Stolperfallen. Erstens: Wenn der Marker fehlt, ist das Payload URL-kodierter Text, kein base64, also ist der Flag keine Förmlichkeit - eine URI, die nach base64 aussieht, aber als percent-kodierter Text generiert wurde, dekodiert zu Müll. Zweitens: Einige Generatoren umbrechen lange Data-URIs mit Zeilenumbrüchen, so wie MIME es tut; Ihr strenger Decoder wird die ablehnen, also streichen Sie Zeilenumbrüche vor dem Dekodieren, wenn die Quelle nicht unter Ihrer Kontrolle ist. Das Dekodieren des Payloads einer data:image/png-URI gibt Ihnen die exakten PNG-Bytes, header inklusive, und das ist die stille Befriedigung der ganzen Übung.

E-Mail, MIME und die 76-Zeichen-Gewohnheit

E-Mail ist der Grund, warum base64 gelernt hat, seine Zeilen umzubrechen. SMTP wurde in seiner ursprünglichen Form gebaut, um Sieben-Bit-ASCII zu tragen, also musste alles Binäre vor der Reise als druckbarer Text neu geschrieben werden. Privacy-Enhanced Mail hat es 1987 mit 64-Zeichen-Zeilen gemacht, und MIME, als es die Kodierung 1993 für E-Mail standardisierte, lockerte die Grenze auf 76 Zeichen und fügte die Regel hinzu, dass ein konformer Decoder Zeilenumbrüche einfach ignorieren muss. Die Gewohnheit überlebte: Ein E-Mail-Anhang ist heute immer noch base64, umgebrochen bei 76, und die exakte Arithmetik ergibt 4/3 mal 78/76 - etwa 137 Prozent der ursprünglichen Größe, plus ein paar hundert Bytes an Headern. Ihr C++-Decoder schrumpft das alles zurück auf 100 Prozent, und das ist der Punkt des ganzen Formats.

Der Haken bei C++ ist, dass die Decoder in diesem Artikel sich über Zeilenumbrüche nicht einigen, und jeder hat einen Grund dafür. Der OpenSSL-Streaming-Decoder überspringt sie überall im Stream, was genau MIMEs Regel ist. Die OpenSSL-One-Shot-Funktion lehnt jeden Leerraum im Payload ab. Boost.Beast stoppt beim ersten Zeilenumbruch, ohne es zu sagen. Die Boost-Iteratoren werfen bei einem einzelnen Leerzeichen. Also, wenn ein Payload aus der E-Mail kommt, ist Ihre erste Entscheidung, welchen Decoder Sie verwenden, oder Sie streichen die Zeilenumbrüche selbst - ein einzeiliger erase-remove-Pass über \r und \n - und lassen jeden Decoder, den Sie mögen, die eigentliche Arbeit machen. Vorab streichen ist die langweilige, zuverlässige Wahl, und sie ist die, die die Wahl Ihres Decoders unabhängig von der Geschichte Ihres Payloads hält.

Datenbanken, Konfigurationsdateien und Umgebungsvariablen

Das dritte Zuhause von base64 ist die Speicherungsschicht: eine Spalte in einer Legacy-Datenbank, deren Dokumentation "base64" sagt und nichts weiter, ein Config-Blob in einer JSON-Datei, ein Payload in einer Umgebungsvariablen, das ein Dienst base64-iert hat, damit es eine Shell überlebt. Das Dekodier-Muster ist dasselbe wie überall - den String lesen, dekodieren, als Bytes behandeln - aber die unbezeichnete Eingabe verdient einen besonderen Absatz, weil man manchmal wirklich nicht weiß, welches Alphabet verwendet wurde. Man kann es nicht wissen, aber man kann testen, weil vier Zeichen die meiste Arbeit machen:

  • Enthält + oder / - nur das Standardalphabet kann recht haben.
  • Enthält - oder _ - nur das URL-safe-Alphabet kann recht haben.
  • Weder noch, endet aber auf = - gepaddetes Standard, oder ein gepaddeter URL-safe-String, dessen Payload nie zufällig die zwei getauschten Zeichen gebraucht hat.
  • Weder noch, kein Padding - könnte beides sein; die rohe URL-safe-Form ist die übliche im Web, also bei Daten, die in einer URL oder einem Token geboren wurden, zuerst diese versuchen.

Strings, die keines der vier unterscheidenden Zeichen verwenden, dekodieren unter beiden Alphabeten identisch, also ist bei ihnen die Reihenfolge, in der Sie sie versuchen, eine Frage davon, woher die Daten kamen: Dinge, die in der E-Mail geboren wurden, wollen das Standardalphabet, Dinge, die in einer URL geboren wurden, wollen das URL-Alphabet. Und denken Sie daran, die gepaddete und die ungepaddete Lesart desselben Strings zu versuchen - ein fehlendes = ist der Unterschied zwischen "abgelehnt" und "gelöst", und deshalb akzeptiert der strenge Decoder oben beides.

Die Kommandozeile hat zwei Base64s

Für Einmal-Jobs hat eine Linux-Kiste normalerweise zwei base64-Decoder, und sie verhalten sich in genau der Weise unterschiedlich, die Leute beißt. Der erste ist base64 von GNU coreutils (einige neuere Distributionen liefern stattdessen die uutils-Reimplementierung aus, und beide sprechen dieselben Flags - mit base64 --version prüfen). Es konformiert sich mit RFC 4648, bricht beim Kodieren bei 76 Zeichen um (mit -w 0 schaltet man das aus), und beim Dekodieren akzeptiert es fröhlich Zeilenumbrüche überall; sein -i-Flag macht die Müll-Toleranz explizit statt versehentlich. Der zweite ist der von OpenSSL, und hier ist der Twist: openssl base64 ist überhaupt kein eigenes App. Seit der 1.1.0-Serie (2016) prüft das enc-Programm seinen eigenen Aufrufnamen, und wenn es als "base64" aufgerufen wurde, schaltet es sich selbst in den base64-Modus - ein String-Vergleich auf argv[0], was die C-Art ist, einen Alias auszuliefern. Ohne -A erwartet es irgendwo in den ersten 1024 Bytes der Eingabe einen Zeilenumbruch, also kommt ein langer einzeiliger String leer zurück, mit Exit-Code 0. Mit -A liest es eine Zeile, und die dokumentierte Bug-Liste für den enc-Befehl ist ein Zweitem-Museum: Die -A-Option funktioniert nicht richtig mit großen Dateien, und ohne -A, wenn die ersten 1024 Bytes keinen Zeilenumbruch halten, werden die ersten zwei Zeilen der Eingabe ignoriert. In einer Pipeline sieht eine stille leere Datei genau aus wie ein erfolgreiches Dekodieren eines leeren Payloads.

# die ehrlichen Einzeiler
base64 -d < payload.b64 > payload.bin
openssl base64 -d -A < payload.b64 > payload.bin

Keiner spricht base64url nativ, was ein weiterer Grund ist, warum das Transcoding-Snippet in Ihrem Muskelgedächtnis gehört. Für alles, was zählt, dekodieren Sie in Ihrem Programm, wo Fehler als Zahlen zurückkommen, die Sie testen können, und der Exit-Code eines stillen Tools nicht Ihr einziges Signal ist.

Fallen, die C++ besonders beißen

  • Die Nullauffüllung. EVP_DecodeBlock gibt drei Bytes für TQ== zurück: den Buchstaben M plus zwei Nullen. Recover die echte Länge aus dem Padding, oder verwenden Sie die Streaming-API, die ehrlich über die Zählung ist.
  • Die Streaming-Kuriosität vor 3.5. In OpenSSL-Releases vor 3.5.0 (April 2025) hatte EVP_DecodeUpdate dieselbe Nullauffüll-Gewohnheit. Code, der gegen eine 3.0- oder 3.3-Pinnung geschrieben wurde, kann Sie über Tail-Längen anlügen; der Fix ist im Geschichts-Abschnitt der Manpage dokumentiert.
  • Der stille Stopp. Boost.Beasts decode hat keinen Fehlerkanal: Es stoppt bei jedem ungültigen Zeichen, jedem Zeilenumbruch und jeder unmöglichen Tail-Länge und gibt ein Teil-Ergebnis mit geradem Gesicht zurück. Prüfen Sie, dass consumed + pads == input.size() und dass die Summe ein Vielfaches von 4 ist, sonst dekodieren Sie, was es gerade beschlossen hat zu dekodieren.
  • Die decoded_size-Falle. b64::decoded_size(n) geht davon aus, dass n durch 4 teilbar ist. Zwei Zeichen Eingabe können ein Byte ergeben, während decoded_size(2) null sagt - fügen Sie für ungerade Längen Spielraum hinzu.
  • Die Null-Byte-Pads. Die Boost-Iteratoren dekodieren = als den Wert null, also wird aus TWFuZQ== sechs Bytes inklusive zwei abschließender Nullen. Subtrahieren Sie die Pad-Anzahl, oder genießen Sie Ihre Gespenster.
  • Leerraum, auf vier Arten. OpenSSL-Streaming überspringt ihn, OpenSSL-One-Shot lehnt ihn intern ab, die Archive-Iteratoren werfen über ihn, und Beast stoppt bei ihm. Eingesetzte Strings lieben es, Leerraum mitzubringen, und jeder Decoder hat seine eigene Meinung darüber.
  • Signierter char-Index. Wenn Sie Ihre eigene Dekodier-Tabelle nach Zeichen indexiert bauen, indexieren Sie mit unsigned char. Auf Plattformen, wo char signiert ist, wird ein Byte über 127 zu einem negativen Index, was undefiniertes Verhalten ist, das einen Laborumhang trägt.
  • Das Minus-Zeichen ist ein Zeitreisender. In OpenSSL ist - eine weiche Eingabe-Ende-Marke aus der PEM-Ära, kein Alphabetzeichen. Transcodieren Sie base64url, bevor Sie dekodieren.
  • int, nicht size_t. Die EVP-Längenparameter sind int. Über 2 GB ist nur der chunk-basierte Streaming-Pfad sicher, und deshalb existiert er.
  • Textmodus auf Windows. Das Öffnen einer Datei für Text übersetzt CRLF zu LF und korrumpiert Ihre Eingabe vor dem Dekodieren. std::ios::binary, jedes Mal, auf jeder Plattform.
  • Der most vexing parse. std::vector<char> v(istreambuf_iterator<char>(f), istreambuf_iterator<char>()) ist eine Funktions-Deklaration. Verwenden Sie die Initialisierung mit geschweiften Klammern oder ein Zeiger-Paar.
  • Nicht-kanonische Pad-Bits. Ein nachsichtiger Decoder kann Strings akzeptieren, deren ungenutzte Pad-Bits nicht null sind, also dekodieren zwei sichtbar verschiedene Strings zu denselben Bytes (Base64-Formbarkeit). An Sicherheitsgrenzen lehnen Sie ab, was Sie nicht brauchen - RFC 4648 sagt, dass Decoder genau das may.
  • Die Kommandozeile versagt still. openssl base64 -d ohne -A verschluckt einzeilige Eingabe (leere Ausgabe, exit 0); die dokumentierten Bugs decken große Dateien und Eingabe ohne Zeilenumbrüche in beide Richtungen ab. Prüfen Sie Ihre Ausgabe in Pipelines.
  • strlen auf Binärdaten. std::string hält Null-Bytes glücklich, aber in dem Moment, in dem Sie einer Legacy-API einen C-String händigen, stoppt strlen beim ersten NUL. Übergeben Sie Länge und Zeiger, nie einen nackten Zeiger.

Eine kurze Geschichte von Base64 in C++

Das Format ist älter als die moderne Ära der Sprache. Die erste standardisierte Verwendung der heute MIME-base64 genannten Kodierung war das Privacy-Enhanced-Mail-Protokoll, vorgeschlagen 1987 mit 64-Zeichen-Zeilen und einem RSA-MD2/MD5-Nachrichtenintegritätscheck, der ans Ende geklebt war; der Name "base64" selbst kam erst 1993 an, als die MIME-Standards ihn benannten. C++ kam als C++98 im Jahr 1998 auf die Bühne - fünf Jahre nach MIME - und der erste Base64-Code, nach dem die Entwickler der Sprache griffen, war das C-Paar von Rene Nyffenegger (2004-2008), das eine Stack-Overflow-Frage vom 4. Dezember 2008 quer durch das Web verbreitete. Der schönste Teil dieser Geschichte ist, wer nicht aufgetaucht ist: Der Autor hat selbst nie eine Antwort gepostet, aber sein Snippet wurde das Volkslied, das alle kopierten. Eine Antwort im Thread druckte seine komplette Implementierung von seiner eigenen Website ab, falls die Seite untergeht.

Dann tat das Ökosystem, was Ökosysteme tun. 2002 lieferte Robert Rameys Boost.Serialization die Iterator-Adapter aus - das älteste Base64 in der C++-Werkzeugkiste, so streng, dass es bei einem einzelnen Leerzeichen eine Ausnahme wirft, ein Jahr, bevor RFC 3548 die Regel kodifizierte, die es bereits durchsetzte. 2017 brachte Boost 1.66 Beast, und mit ihm den header-only-Codec, der heute noch ausgeliefert wird, mit der Nyffenegger-Zuschreibung im Footer. Derweil ging der Standard selbst C++11, C++14, C++17, C++20 und C++23 (veröffentlicht 2024) durch, und jedes einzelne von ihnen schaute auf das 64-Zeichen-Alphabet und ging weiter. C++26 fügt einen neuen <text_encoding>-Header für Text-Codec-Arbeit hinzu, noch 2022 genehmigt; sein technischer Inhalt wurde auf dem ISO-Meeting im März 2026 in London fertiggestellt, wo der Ausschuss mit 114-12-3 dafür stimmte, ihn zur Veröffentlichung zu schicken, und die späteren Meetings des Ausschusses in 2026 - einschließlich des einen in Búzios, Brasilien, am 16.-21. November - werden damit verbracht, den nächsten Arbeitsentwurf, C++29, zu eröffnen, statt über diesen abzustimmen. Base64 war nie im Entwurf. Sieben Standards, drei Jahrzehnte, ein Header für Textkodierung - und der Ausschuss hatte jetzt jeden denkbaren Vorwand, base64 hinzuzufügen, und hat alle ausgeschlagen. Die praktische Geschichte von Base64 in C++ ist und bleibt die Geschichte seiner Bibliotheken: OpenSSLs EVP-Routinen, zwei Boost-Varianten, ein Windows-API-Aufruf und ein vierzigzeiliges Snippet, das Sie besitzen.

Fun Facts, C++-Edition

  • Dasselbe Funktionspaar taucht in den Antworten auf eine Stack-Overflow-Frage aus 2008 auf, in der Quelle von Boost.Beast mit einem Zuschreibungs-Footer und in den Header-Dateien unzähliger privater Codebases. Fragen Sie einen C++-Entwickler, woher sein base64 stammt, und die ehrlichste Antwort ist "Das weiß ich nicht, und das Internet auch nicht".
  • Boosts Archive-Iteratoren sind das älteste Base64 in diesem Artikel, Urheberrecht 2002 - dasselbe Jahr, in dem das .NET-Framework-1.0-SDK ausgeliefert wurde. Sie werfen bei einem einzelnen Leerzeichen eine Ausnahme, was bedeutet, dass sie die Regel "Nicht-Alphabet-Zeichen ablehnen" durchsetzten, bevor die RFCs aufholten: RFC 3548 kodifizierte sie 2003, und RFC 4648 wiederholte sie 2006.
  • OpenSSLs Streaming-Decoder arbeitet von einem internen 80-Byte-Buffer, aber er gibt alle 64 Base64-Zeichen aus, dieselbe Zeilenbreite, die PEM-Rüstung seit 1987 verwendet. Dieses stille 64 ist einer der letzten Orte, wo das alte Format 2026 noch tragende Arbeit leistet.
  • Das kleinste mögliche gepaddete Base64 sind vier Zeichen, TQ==: ein Byte im zwei-Zeichen-Kostüm. Das kleinste ungepaddete sind zwei Zeichen, TQ. Welches Sie zu dekodieren bekommen, hängt komplett davon ab, wer es kodiert hat, und dabei war an Sie nicht gedacht.
  • MIMEs Mathematik ist exakt: 4/3 mal 78/76, deshalb trifft ein E-Mail-Anhang etwa 137 Prozent seiner ursprünglichen Größe ein (plus ein paar hundert Bytes an Headern obendrauf). Ihr C++-Decoder schrumpft es wieder auf 100 Prozent, und das ist die stille Freude der ganzen Übung.
  • Auf einem typischen libstdc++ oder MSVC trägt std::string kleine Payloads in einem Stack-Buffer über die Small-String-Optimierung, statt zu allozieren. Eine 9-Byte-Eingabe dekodiert zu 6 Bytes und berührt nie den Heap. Die Base64-Form Ihres winzigen Config-Blobs kann wörtlich in einem Stack-Frame leben, und das ist die Art von Gratis-Lunch, die die Standardbibliothek nicht bewirbt.
  • Der openssl base64-Befehl, nach dem Sie in einer Shell greifen könnten, ist überhaupt kein Befehl. Es ist das enc-Programm, das seinen eigenen Namen in argv[0] prüft und die Persönlichkeit wechselt. Ein Alias durch String-Vergleich, was die C++-Art ist, Dinge zu machen, in C.
  • YouTube-Video-Identifikatoren sind base64url: elf Zeichen, kein Padding, kein + oder / in der Nähe einer URL. Das meistangesehene Kodierformat auf dem Planeten läuft auf der "URL- und Dateinamen-sicheren" Variante, die RFC 4648 in einem Abschnitt hinzugefügt hat, der auf eine Seite passt.

Wenn Sie stattdessen verpacken müssen

Alles, was Sie gerade dekodiert haben, wurde von derselben Werkzeugkiste auf der anderen Seite verpackt: EVP_EncodeBlock für One-Shots, EVP_EncodeUpdate plus EVP_EncodeFinal für Streams (und da kommen diese 64-Zeichen-Zeilen her), dieselbe Buffer-Arithmetik in umgekehrter Richtung und dieselbe 33-Prozent-Steuer, die das Dekodieren still erstattet. Die komplette Verpackungs-Geschichte - die Größen-Mathematik im Einzelnen, die Encoder, die ihre Ausgabe null-terminieren, der Boost-Iterator, der ein Pad-Zeichen nie kennengelernt hat, base64url, MIME-Umbruch, Dateien und die Windows-API mit ihrer CRLF-Gewohnheit - lebt im C++-Kodierungs-Leitfaden auf der Schwester-Site. Gehen Sie ihn lesen, und kommen Sie dann zurück und öffnen Sie etwas Großes. Das ist das ganze Spiel: keine Standardbibliothek, drei vertrauenswürdige Anbieter mit drei unterschiedlichen Temperamenten, ein Decoder, der auf das exakte Byte zeigt, das wehgetan hat, ein 2025er-Bugfix, der den Streaming-Tail verändert hat, und ein nullgefülltes Dreier-Set, das man für immer im Kopf behält. Viel Spaß beim Auspacken.

Zuletzt aktualisiert: 2026-09-08

Verwandter Artikel: Base64-Kodierung in C++ (Cpp): Ein vollständiger Leitfaden