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-Kodierung in C: Ein vollständiger Leitfaden

Sie haben Bytes. Vielleicht sind es ein JPEG, gelesen von der Platte, vielleicht ein Token, vielleicht ein Passwort, das ein Client gleich übertragen wird, vielleicht die rohen Bytes einer Datei, die eine Pipeline in einem JSON-Feld erwartet. Und irgendwann weiter hinten auf dem Weg gibt es einen Kanal, der nur Text spricht: ein JSON-String, eine URL, ein E-Mail-Body, eine Konfigurationsdatei, eine Datenbank-Spalte, die tut, als wäre sie Text. Genau dort kommt die Base64-Kodierung ins Spiel: Sie schreibt jeweils drei Bytes Rohdaten als vier Zeichen aus einem 64-Buchstaben-Alphabet um, so dass das Ergebnis schlichtes ASCII ist, das jede Text-Pipeline der Erde überlebt. Die Startseite dieser Site erklärt das Format vollständig; dieser Artikel handelt davon, den Job in C gut zu machen, wo - wie üblich - die Sprache es nicht für Sie tut.

Die eine Zahl, die Sie im Kopf behalten sollten: Kodierung vergrößert Ihre Daten. Drei Bytes werden zu vier Zeichen, also verlässt jeder Payload Ihr Programm etwa ein Drittel größer, plus ein wenig mehr, wann immer Zeilenumbrüche hinzukommen. Das ist die Steuer, und es gibt keinen Weg daran vorbei - aber in C hat die Steuer einen eigenen Posten, denn Sie allozieren den Ausgabe-Puffer selbst, und der Puffer muss exakt groß genug sein. Wenn Sie die Mathematik einmal richtig haben, wird jeder Encoder in diesem Artikel vorhersehbar: keine Überläufe, keine Unterläufe, kein Rätseln, wo das nächste Byte landet. Dann gibt es die Werkzeugkiste, aus der Sie wählen können - OpenSSL, Mbed TLS, APR-Util, GLib - und die Wahl zählt, denn jeder bricht anders um, terminiert anders und meldet Fehler anders.

Vier Encoder, vier Persönlichkeiten

Alle vier Bibliotheken kodieren das Standard-Alphabet korrekt und identisch - gleiche Bytes rein, gleiche Zeichen raus, immer. Die Unterschiede liegen in der Verpackung, und in der Verpackung verstecken sich Interoperabilitäts-Bugs. Hier ist die Landschaft:

Bibliothek Header Ausgabe-Stil Fehler-Modus
OpenSSL (libcrypto) <openssl/evp.h> Keine Zeilenumbrüche; schreibt einen NUL-Terminator Praktisch keiner (nur Allokation)
Mbed TLS <mbedtls/base64.h> Keine Zeilenumbrüche; NUL-terminiert Buffer-zu-klein-Code mit der benötigten Größe
APR-Util <apr-1.0/apr_base64.h> Keine Zeilenumbrüche; hängt ein NUL an Keiner - vertrauen Sie Ihrer Puffer-Größe
GLib <glib.h> Keine Zeilenumbrüche; NUL-terminiert, heap-alloziert Gibt NULL zurück (nur Allokation)

Achten Sie darauf, was in der Tabelle fehlt: Keine von ihnen bricht Zeilen standardmäßig um. Das ist so gewollt - RFC 4648 sagt, dass Implementierungen keine Zeilenumbrüche hinzufügen dürfen, es sei denn, die umgebende Spezifikation bittet ausdrücklich darum - und es ist eine Erleichterung, denn ein verirrter Zeilenumbruch in einem JSON-String oder einer URL ist ein Fehler, kein Feature. Der Umbruch existiert für E-Mail und PEM, und wenn Sie ihn brauchen, holen Sie ihn aus dem Streaming-Weg von OpenSSL oder Sie brechen selbst in fünf Zeilen um (der E-Mail-Abschnitt zeigt beides). Für die Wahl einer Bibliothek: Nehmen Sie OpenSSL, wenn Sie sie ohnehin schon linked haben, Mbed TLS für Embedded-Builds, in denen über jedes Kilobyte gestritten wird, APR-Util im Apache-Ökosystem und GLib, wenn der Rest Ihres Programms ohnehin schon GLib ist. Installation: libssl-dev (Debian/Ubuntu) oder openssl-devel (Fedora/RHEL) oder brew install openssl (macOS); libmbedtls-dev für Mbed TLS; libaprutil1-dev plus libapr1-dev für APR-Util; glib2.0-dev für GLib.

Die Mathematik, bevor Sie allozieren

Bevor es Code gibt, die Arithmetik, denn C wird Sie vor einem zu kleinen Puffer nicht retten. Jedes drei Eingabe-Bytes erzeugen exakt vier Ausgabe-Zeichen. Wenn die Eingabelänge kein Vielfaches von drei ist, erzeugt die letzte Gruppe trotzdem vier Zeichen, und die ungenutzten Slots werden mit =-Pads markiert: ein Eingabe-Byte wird zu vier Zeichen mit zwei Pads, zwei Eingabe-Bytes zu vier Zeichen mit einem Pad. Also ist die exakte kodierte Länge für n Bytes:

size_t encoded_chars(size_t n) {
  return ((n + 2) / 3) * 4;
}

Für 1000 Bytes sind das 1336 Zeichen; für 1 Byte sind es 4; für 0 sind es 0. Zwei Anpassungen folgen daraus. Erstens: OpenSSL und Mbed TLS hängen beide einen NUL-Terminator nach den Daten an (und Mbed TLS reserviert den Platz dafür, wenn Sie die Größe abfragen), also will Ihr Puffer ein zusätzliches Byte: encoded_chars(n) + 1. Zweitens: Wenn Sie eine umgebrochene Ausgabe wollen, fügen Sie pro Zeile einen Zeilenumbruch hinzu: Der Streaming-Encoder von OpenSSL gibt für jedes 48 Eingabe-Bytes eine 64-Zeichen-Zeile aus, also ist die umgebrochene Länge encoded_chars(n) + (n + 47) / 48. Prüfen Sie mit 1000 Bytes: 1336 Zeichen plus 21 Zeilenumbrüche sind 1357, und genau das produziert der Encoder. Schreiben Sie die Formel einmal als Funktion und verwenden Sie sie überall; sie ist der Unterschied zwischen "es passt" und einer Heap-Korruption um 3 Uhr nachts.

size_t b64_buffer_size(size_t in_len) {
  return ((in_len + 2) / 3) * 4 + 1; /* Zeichen + NUL */
}
size_t b64_buffer_size_wrapped(size_t in_len) {
  return ((in_len + 2) / 3) * 4 + (in_len + 47) / 48 + 1;
}

OpenSSL: Ein Block oder ein laufender Hahn

Die One-Shot-Funktion von OpenSSL ist das Arbeitspferd, und sie ist die freundlichste der ganzen Bande:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  const char *text = "Mane";
  unsigned char out[32];
  int n = EVP_EncodeBlock(out, (const unsigned char *)text,
                          (int)strlen(text));
  printf("len=%d str=%s\n", n, (char *)out);
  return 0;
}

Sie schreibt die kodierten Zeichen nach out, hängt ein NUL dahinter an und gibt die Länge ohne das NUL zurück - damit ist das Ausgeben mit %s sicher, und die Länge ist verfügbar, wenn Sie sie brauchen. Der Ausgabe-Puffer muss encoded_chars(n) + 1 Bytes fassen. Es gibt keinen Fehler-Pfad, den Sie behandeln müssten: Kodieren kann nicht fehlschlagen, denn jedes Byte ist legale Eingabe, und die Funktion hat kein Eingabe-Validierungs-Konzept, an dem sie sich stoßen könnte. Der einzige Weg, auf dem sie schiefgehen kann, ist, dass Sie ihr einen zu kleinen Puffer geben - und der Mathematik-Abschnitt ist das Gegengift.

Das Streaming-Paar ist für den Fall gedacht, dass die Daten groß sind oder in Stücken ankommen. EVP_EncodeUpdate verarbeitet Eingabe in 48-Byte-Blöcken und schreibt pro vollständigem Block 64 Zeichen plus einen Zeilenumbruch (65 Bytes) und hält den Rest im Kontext auf, bis mehr Daten oder der finale Aufruf kommt:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
  if (ctx == NULL) {
    return 1;
  }
  EVP_EncodeInit(ctx);
  unsigned char in[1000];
  for (int i = 0; i < 1000; i++) {
    in[i] = (unsigned char)(i % 251);
  }
  unsigned char out[1400]; /* 1336 Zeichen + 21 Zeilenumbrüche + Reserve */
  int outl = 0;
  int total = 0;
  EVP_EncodeUpdate(ctx, out + total, &outl, in, 1000);
  total += outl;
  EVP_EncodeFinal(ctx, out + total, &outl);
  total += outl;
  int nl = 0;
  for (int i = 0; i < total; i++) {
    if (out[i] == '\n') nl++;
  }
  printf("encoded 1000 bytes into %d chars, %d newlines\n",
      total, nl);
  EVP_ENCODE_CTX_free(ctx);
  return 0;
}

Die Ausgabe dieses Programms sind 1357 Bytes mit 21 Zeilenumbrüchen - die Formel aus dem Mathematik-Abschnitt, real gemacht. Zwei praktische Notizen. Der Versions-Hinweis: Seit OpenSSL 1.1.0 (2016) ist der Kontext-Typ opak, also allozieren Sie mit EVP_ENCODE_CTX_new() und geben Sie mit EVP_ENCODE_CTX_free() frei; das ältere Stapel-Muster EVP_ENCODE_CTX ctx;, das Sie in Tutorials finden, die OpenSSL 1.0.2 und älter targeten, kompiliert nicht gegen moderne Header, OpenSSL 3.x inklusive. Und der Design-Hinweis: Weil nur vollständige 48-Byte-Blöcke aus EVP_EncodeUpdate austreten, füttert die sauberste Chunk-Pipeline ihn mit Vielfachen von 48 - dann ist jede Zeile, die die Funktion schreibt, eine fertige Zeile, und EVP_EncodeFinal allein entscheidet, wie der Tail umgebrochen wird. Wenn Ihre Eingabe in beliebigen Größen ankommt (ein Netzwerk-Read), erledigt der Kontext das Ausrichten trotzdem für Sie; die Gewohnheit mit den Vielfachen von 48 ist nur das, was die Ausgabe vorhersehbar macht.

Mbed TLS: Fragen, dann kodieren, einen String bekommen

Das Encode von Mbed TLS hat den saubersten Vertrag in der Gruppe, aufgebaut um eine Größenanfrage, die Sie mit einem NULL-Ziel aufrufen können:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <mbedtls/base64.h>
int main(void) {
  const char *text = "Mane";
  size_t slen = strlen(text);
  size_t needed = 0;
  int rc = mbedtls_base64_encode(NULL, 0, &needed,
      (const unsigned char *)text, slen);
  if (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
    printf("size query failed: %d\n", rc);
    return 1;
  }
  printf("needs %zu bytes\n", needed);
  unsigned char *out = malloc(needed);
  size_t olen = 0;
  rc = mbedtls_base64_encode(out, needed, &olen,
      (const unsigned char *)text, slen);
  if (rc != 0) {
    printf("encode failed: %d\n", rc);
    free(out);
    return 1;
  }
  printf("olen=%zu str=%s\n", olen, out);
  free(out);
  return 0;
}

Lesen Sie die Details sorgfältig, denn sie sind eine Meisterklasse in einer freundlichen API. Die Größenanfrage meldet needed als die kodierten Zeichen plus eins für das NUL - für "Mane" sind das 8 plus 1, also 9 - und sie meldet sich mit dem "Buffer zu klein"-Code (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL, das ist -0x002A), weil ein NULL-Ziel per Definition zu klein ist. Der echte Aufruf schreibt dann die Zeichen und das NUL, und *olen kommt als 8 zurück - die Länge ohne den Terminator - so dass der Puffer bereits ein druckbarer C-String ist. Wenn Sie ihm einen Puffer geben, der ein Byte zu kurz ist, bekommen Sie denselben "zu klein"-Code zurück, mit der benötigten Größe in *olen, so dass Ihnen der Fehlschlag genau sagt, um wie viel Sie zu kurz lagen. Noch eine Notiz: Die Bibliothek macht ihre Tabellen-Lookups über konstantzeitige Helfer, ein kleiner Schuss Sorgfalt, den Sie in den meisten Encodern nicht sehen werden.

APR-Util und GLib: Die anderen zwei

Der Encoder von APR-Util ist ein schlichtes Paar von Funktionen mit int-Längen:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <apr-1.0/apr_base64.h>
int main(void) {
  const char *text = "Mane";
  int needed = apr_base64_encode_len((int)strlen(text));
  char *out = malloc((size_t)needed);
  int n = apr_base64_encode(out, text, (int)strlen(text));
  printf("n=%d (includes NUL) str=%s\n", n, out);
  free(out);
  return 0;
}

Hier zählen sowohl apr_base64_encode_len() als auch der Rückgabewert das NUL, also ist n eins mehr als die Zeichenanzahl - ein Buchhaltung-Unterschied zu OpenSSL und Mbed TLS, der in mehr als einem Codebase Off-by-one-Bugs produziert hat. Die gleiche 32-Bit-Längenbeschränkung gilt: Für Werte nahe an oder über 2 GB ist das nicht das richtige Werkzeug. Es gibt auch apr_base64_encode_binary(), das auf EBCDIC-Maschinen die EBCDIC-zu-ASCII-Umwandlung der Eingabe überspringt - auf den Mainframes, wo diese Umwandlung ansonsten passiert, und überall sonst ein no-op-Unterschied. Das Paar, die Binary-Variante und die passenden Decode-Funktionen sind in der Tat die gesamte base64-Oberfläche, die apr-util bietet: Es gibt keine Pool-allozierte Variante und keine Streaming-Variante, also ist das malloc-Muster von oben das einzige Muster.

Der Encoder von GLib ist der heap-allozierte Stil - Sie bekommen einen NUL-terminierten String und eine Pflicht:

#include <stdio.h>
#include <glib.h>
int main(void) {
  const char *text = "Mane";
  gchar *enc = g_base64_encode((const guchar *)text, strlen(text));
  printf("%s\n", enc);
  g_free(enc);
  return 0;
}

Kein Umbruch, NUL-terminiert, freigeben mit g_free - die G_GNUC_MALLOC-Anmerkung am Prototyp ist das, was statischen Analyzern das sagt. Wenn Sie Zeilenumbrüche wollen, ist das inkrementelle Paar das Werkzeug: g_base64_encode_step() nimmt eine Zustands-Ganzzahl und eine break_lines-Flagge und sagt Ihnen, wie viele Ausgabe-Bytes es geschrieben hat, und g_base64_encode_close() beendet die letzte Teilgruppe. Das ist die gleiche Zustandsautomaten-Form wie das Streaming-Paar von OpenSSL, nur mit GLibs Parameter-Stil.

Handgemachtes URL-sicheres Base64

Die vier Bibliotheken von oben sprechen alle das Standard-Alphabet: A-Z, a-z, 0-9, plus und Schrägstrich. Das Web allerdings spricht zunehmend den zweiten Dialekt aus Abschnitt 5 von RFC 4648, genannt base64url: dieselbe Kodierung, aber + wird durch - getauscht und / durch _, und das nachgestellte =-Padding wird weggelassen, wenn die Länge bekannt ist. JSON Web Tokens, OAuth-State-Parameter und unzählige API-IDs verwenden es, denn + und / sind beide gefährlich in URLs, während - und _ nicht reservierte Zeichen sind, die ohne Reibungsverluste durchfahren. Da keine C-Bibliothek diesen Dialekt nativ ausspuckt, machen Sie ihn selbst - und das sind zwei kleine Änderungen, denn Sie haben bereits einen Standard-Alphabet-Encoder:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
static void to_base64url(const char *std_b64, char *out, size_t out_cap) {
  size_t i = 0;
  for (const char *p = std_b64; *p && *p != '='; p++) {
    char c = *p;
    if (c == '+') c = '-';
    if (c == '/') c = '_';
    out[i++] = c;
  }
  out[i] = '\0'; /* Padding wird bewusst verworfen */
}

Die Nutzung ist zwei Schritte - erst Standard kodieren, dann übersetzen:

unsigned char enc[32];
EVP_EncodeBlock(enc, (const unsigned char *)"hi>there", 8);
char url_safe[32];
to_base64url((const char *)enc, url_safe, sizeof(url_safe));
printf("%s\n", url_safe); /* aGk-dGhlcmU */

Zwei Vorsicht-Notizen. Die Schleife hält beim ersten = an, und das ist es, was das Padding weglässt - "korrigieren" Sie das nicht, das Padding wegzulassen ist der Punkt (der Empfänger, der es braucht, kann es aus der Länge wieder ergänzen). Und dimensionieren Sie out auf die volle kodierte Länge, nicht weniger: Die Übersetzung geht zeichenweise bis zu den Pads, also ist die Kapazität, die Sie für die Standardform schon alloziert haben, exakt richtig. Eine ehrliche Notiz für die Interoperabilität: Wenn Ihre Daten zufällig keine Bytes enthalten, die auf + oder / mappen, sind die Standard- und die URL-sichere Form identisch, und nichts wird je über eine Verwechslung klagen - der Bug zeigt sich erst, wenn die Daten endlich eines enthalten. Behandeln Sie den Dialekt als Eigenschaft des Kanals (URLs, Tokens), nicht der Daten.

Text und Charsets: UTF-8 sind einfach nur Bytes

Eine Frage, die C-Einsteiger überrascht: Was passiert mit akzentuiertem Text, Emoji, CJK-Zeichen? Die Antwort ist die befreiste Tatsache in diesem Artikel - es muss nichts passieren. Base64 arbeitet auf Bytes, und C ist eine Sprache von Bytes. Wenn Ihr Text UTF-8 ist (was er 2026 vermutlich ist), ist die UTF-8-Kodierung von "café" fünf Bytes - 63 61 66 c3 a9 - und Base64 kodiert diese fünf Bytes exakt wie jedes andere fünf Bytes und erzeugt Y2Fmw6k=. Kein Charset-Parameter, kein BOM, kein Umwandlungsschritt, kein Bibliotheksaufruf. Der Codec weiß nicht und kümmert sich nicht darum, was die Bytes bedeuten; das ist das gesamte Design.

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  const char *utf8 = "caf\303\251"; /* café als UTF-8 */
  unsigned char enc[32];
  int n = EVP_EncodeBlock(enc, (const unsigned char *)utf8,
                          (int)strlen(utf8));
  printf("%.*s\n", n, (char *)enc); /* Y2Fmw6k= */
  return 0;
}

Zwei Fallen sitzen an den Rändern dieses Abschnitts. Die erste ist wchar_t: Wenn Ihre Daten als Wide-Characters angekommen sind, müssen Sie sie erst in eine Byte-Folge umwandeln (auf Linux, UTF-8, via wcstombs() oder Ihrer Locale-Maschinerie), bevor Sie kodieren - Base64 eines wchar_t-Arrays ist die Kodierung einer internen Repräsentation, nicht des Textes, und sie wird zwischen Plattformen unterschiedlich sein. Die zweite ist die Quell-Kodierung: Der String-Literal in Ihrer C-Datei ist in der Kodierung der Quelldatei kodiert (UTF-8 in jedem modernen Projekt), also funktioniert das direkte Schreiben von "café", solange die Datei wirklich UTF-8 ist und Ihr Compiler das weiß (tut er, standardmäßig, in modernen Toolchains). Kodieren Sie die Bytes, die Sie senden wollen, und lassen Sie den Empfänger damit umgehen, was die Bytes bedeuten.

Bilder: Vom Puffer zum String

Der häufigste "echte" Kodier-Job in Web-C: Eine binäre Datei - ein JPEG, ein PNG, ein Icon - muss durch einen Text-Kanal reisen, also wird sie zu einem Base64-String. Das Rezept lautet: Die Datei in einen Puffer lesen, die Ausgabe mit der Formel dimensionieren, kodieren und weitermachen. Die Dateilese-Hälfte verdient Sorgfalt, denn dort brechen C-Programme tatsächlich:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/evp.h>
int main(void) {
  FILE *f = fopen("photo.png", "rb");
  if (f == NULL) {
    return 1;
  }
  fseek(f, 0, SEEK_END);
  long size = ftell(f);
  fseek(f, 0, SEEK_SET);
  unsigned char *data = malloc((size_t)size);
  size_t got = fread(data, 1, (size_t)size, f);
  fclose(f);
  size_t out_cap = ((got + 2) / 3) * 4 + 1;
  unsigned char *enc = malloc(out_cap);
  int n = EVP_EncodeBlock(enc, data, (int)got);
  printf("png of %zu bytes becomes %d base64 chars\n", got, n);
  free(data);
  free(enc);
  return 0;
}

Notizen: rb zum Binär-Lesen - nicht verhandelbar auf jeder Plattform, denn Text-Modus kann Bytes übersetzen und got ändern; das fseek/ftell-Paar zum Größenmessen (für Pipes und Sockets ohne Seek lieber in einen wachsenden Puffer lesen); und got statt size für das Kodieren, denn ein kurzer Read ist eine echte Möglichkeit. Ein 1-MB-Bild wird zu etwa 1,33 MB Text - das ist die Steuer, im Voraus berechnet, und deshalb sollte ein base64-in-JSON-Bild-Payload Sie innehalten lassen und fragen, ob ein echter Datei-Upload billiger gewesen wäre.

Dateien und die .b64-Gewohnheit

Die andere Seite des Bild-Jobs: Sie müssen die Base64-Form einer Datei auf die Platte schreiben - einen .b64-Sidecar, ein Backup eines Binären in einem text-sicheren Store, einen Anhang für einen Mailer. Gleiche Mathematik, anderer Writer. Die Gewohnheit, die sich lohnt: die Text-Ausgabe mit expliziten Zeilenumbrüchen in einer Länge zu schreiben, die die Empfängerseite erwartet - 76 für E-Mail, 64 für PEM-artige Konsumenten, oder gar keine, wenn der Empfänger Ihr eigener Code ist:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  FILE *f = fopen("data.bin", "rb");
  if (f == NULL) {
    return 1;
  }
  fseek(f, 0, SEEK_END);
  long size = ftell(f);
  fseek(f, 0, SEEK_SET);
  unsigned char *data = malloc((size_t)size);
  size_t got = fread(data, 1, (size_t)size, f);
  fclose(f);
  unsigned char *enc = malloc(((got + 2) / 3) * 4 + 1);
  int n = EVP_EncodeBlock(enc, data, (int)got);
  FILE *out = fopen("data.b64", "w");
  for (int i = 0; i < n; i += 76) {
    int chunk = i + 76 < n ? i + 76 : n;
    fwrite(enc + i, 1, (size_t)(chunk - i), out);
    fputc('\n', out);
  }
  fclose(out);
  free(data);
  free(enc);
  return 0;
}

Die Schleife schreibt 76-Zeichen-Zeilen und eine letzte kürzere Zeile; ein Dekodierer, der Weißraum überspringt (jeder ernsthafte tut das), wird sich überhaupt nicht um die Zeilenlänge kümmern - deshalb ist der Empfänger derjenige, den man befragt, nicht Ihr Geschmack. Lassen Sie die Ausgabedatei auf der Schreibseite im Text-Modus (w), wenn Sie Plattform-Zeilenumbrüche wollen, oder wb, wenn der Empfänger Zeichen streng zählt - und wenn er zählt, will er exakt das, was Sie versprochen haben: 76 Zeichen plus einen Zeilenumbruch, nichts anderes. Dieses Versprechen, nicht die Bytes, macht aus einer .b64-Datei ein Format.

Data-URIs: Einbetten, richtig

Data-URIs (RFC 2397) sind die andere Seite vom Lieblings-Empfang des Decode-Artikels: Statt data:image/png;base64,... zu empfangen, bauen Sie einen. Die Form ist data:, der Medientyp, ;base64, ein Komma, der Payload - und das Bauen in C ist ein snprintf nach dem Encode:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  /* die 8-Byte-PNG-Signatur */
  const unsigned char png_sig[8] =
    { 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n' };
  unsigned char enc[32];
  int n = EVP_EncodeBlock(enc, png_sig, 8);
  char uri[96];
  snprintf(uri, sizeof(uri), "data:image/png;base64,%.*s",
      n, (char *)enc);
  printf("%s\n", uri);
  return 0;
}

Drei Design-Notizen. Der Medientyp, den Sie in die URI schreiben, ist eine Behauptung, für die Sie verantwortlich sind - sniffen Sie zuerst die Magic-Bytes der echten Datei, denn ein data:image/png, das ein JPEG trägt, verwirrt jeden Konsumenten auf seine eigene Weise. Die ;base64-Flagge ist Pflicht, wenn der Payload Base64 ist; lassen Sie sie weg, muss der Payload stattdessen percent-kodierter Text sein, was ein anderes Format überhaupt ist. Und die eigene Anweisung des RFC lautet, dass Data-URIs für kurze Werte gedacht sind: Ein 5-MB-Logo inline in einer HTML-Seite einzubetten funktioniert, aber es ist ein schlechter Geruch im Design, den eine echte Asset-URL beheben würde. Derselbe Aufbau taucht ständig in JSON-APIs auf, wo ein Client einen Avatar im selben Request wie die Formular-Daten will - kodieren, voranhängen, senden.

HTTP und JSON: Payloads, die überleben

Der größte moderne Grund, in C zu kodieren, ist JSON. Ein JSON-String ist eine Zeichenfolge mit Escaping-Regeln, und rohe Bytes passen nicht hinein: Ein NUL mitten in einem String-Literal ist ein C-Problem, ein buchstäblicher Zeilenumbruch in einem JSON-String ist ungültiges JSON, und beliebige Bytes brauchen eine definierte Escape-Geschichte. Base64 umgeht das ganze Problem, indem es nur Zeichen erzeugt, die JSON nie escapen muss - die 64 Alphabetzeichen plus, im Standard-Dialekt, =, und keines davon ist ein Anführungszeichen oder ein Backslash. Das Binäre geht als String rein und kommt auf der anderen Seite genau so raus, wie es reingegangen ist:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  unsigned char enc[64];
  int n = EVP_EncodeBlock(enc, (const unsigned char *)"hello", 5);
  char json[160];
  snprintf(json, sizeof(json),
      "{\"avatar\": \"%.*s\"}", n, (char *)enc);
  printf("%s\n", json);
  return 0;
}

Das druckt {"avatar": "aGVsbG8="} - ein komplettes, gültiges JSON-Objekt, keine Escaping-Maschinerie beteiligt, und die %.*s-Präzision hält die Länge exakt, auch wenn Sie irgendwann auf einen Encoder umsteigen, der nicht NUL-terminiert. Die ehrliche Kosten sind die Größe: Jedes Byte, das Sie als JSON-String verschiffen, kostet Sie 4/3 eines Bytes Luft plus den Feldnamen und die Anführungszeichen, also wird aus einem 10-kB-Binären ein 13,3-kB-String im JSON. Für gelegentliche kleine Blobs (Icons, Thumbnails, Signaturen, Tokens) ist das ein fairer Preis; für einen 500-MB-Upload ist es eine Architektur, die Sie bereuen werden, und ein echter Datei-Upload ist das Werkzeug für diesen Job. Auch das verdient eine Zeile: Das + und / des Standard-Alphabets sind in einem JSON-String sicher, aber wenn derselbe String später in einer URL-Query reist, sind sie es nicht - das ist der Job des URL-sicher-Abschnitts.

JWTs: Drei Teile, ein Alphabet

Der Flaggschiff-Konsument von base64url in C ist das JSON Web Token. Ein kompaktes JWT laut RFC 7519 sind drei base64url-kodierte Teile, verbunden durch Punkte - Header, Payload, Signatur - und das Bauen eines ist eine angenehme Übung, denn jedes Stück ist eine Funktion, die Sie schon haben: Standard kodieren, ins URL-sichere übersetzen, signieren, wiederholen. Hier ist ein HS256-Token, gebaut mit OpenSSls HMAC:

#include <stdio.h>
#include <string.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
static void to_base64url(const char *std_b64, char *out, size_t out_cap) {
  size_t i = 0;
  for (const char *p = std_b64; *p && *p != '='; p++) {
    char c = *p;
    if (c == '+') c = '-';
    if (c == '/') c = '_';
    out[i++] = c;
  }
  out[i] = '\0';
}
int main(void) {
  const char *secret = "my-hmac-secret-key";
  const char *header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
  const char *payload = "{\"sub\":\"114365\",\"name\":\"Alice\"}";
  unsigned char hb[64], pb[64];
  EVP_EncodeBlock(hb, (const unsigned char *)header,
                  (int)strlen(header));
  EVP_EncodeBlock(pb, (const unsigned char *)payload,
                  (int)strlen(payload));
  char hu[64], pu[64];
  to_base64url((const char *)hb, hu, sizeof(hu));
  to_base64url((const char *)pb, pu, sizeof(pu));
  char signing_input[256];
  snprintf(signing_input, sizeof(signing_input), "%s.%s", hu, pu);
  unsigned char mac[EVP_MAX_MD_SIZE];
  unsigned int mac_len = 0;
  HMAC(EVP_sha256(), secret, (int)strlen(secret),
      (const unsigned char *)signing_input,
      (size_t)strlen(signing_input), mac, &mac_len);
  unsigned char mb[64];
  EVP_EncodeBlock(mb, mac, (int)mac_len);
  char mu[128];
  to_base64url((const char *)mb, mu, sizeof(mu));
  printf("%s.%s.%s\n", hu, pu, mu);
  return 0;
}

Zwei Dinge lehrt die Struktur. Erstens: Die Signatur-Eingabe sind die zwei URL-sicheren Teile, verbunden durch einen Punkt - exakt die Bytes, die der Empfänger sehen wird - also muss die Übersetzung nach base64url vor dem Signieren passieren, nicht danach; signieren Sie die Standard-Alphabet-Form, und die Verifikation des Empfängers scheitert, was ein Bug ist, der kompiliert, läuft und wie ein Schlüssel-Mismatch aussieht. Zweitens: Der Header und der Payload sind schlichtes JSON in einer Base64-Hülle: Jeder kann sie lesen, und das ist das Design. Ein Token ist ein signierter Zettel, kein versiegelter Umschlag - also legen Sie nichts hinein, dessen Lesen Sie einem abgreifenden Benutzer übel nehmen würden, und legen Sie niemals, niemals ein Passwort in einen JWT-Payload, "weil es kodiert ist". Der Base64-Teil dieses Jobs ist klein und langweilig, und das ist das höchste Kompliment, das man einer JWT-Implementation machen kann.

HTTP Basic Auth: Das Token bauen

Der älteste Authentifizierungs-Header ist auch der einfachste Base64-Job: username:password, standard-Alphabet-kodiert, hinter dem Wort Basic. Das Bauen in C sind zwei Zeilen, und die einzige Feinheit ist, dass das Passwort einen Doppelpunkt enthalten darf (und die Aufteilung auf der Empfängerseite muss beim ersten passieren):

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  const char *user = "alice";
  const char *pass = "s3cr3t";
  char creds[128];
  snprintf(creds, sizeof(creds), "%s:%s", user, pass);
  unsigned char enc[160];
  int n = EVP_EncodeBlock(enc, (const unsigned char *)creds,
                          (int)strlen(creds));
  printf("Authorization: Basic %.*s\n", n, (char *)enc);
  return 0;
}

Das druckt Authorization: Basic YWxpY2U6czNjcjN0. Die Warnung des RFC gilt auch auf der Absenderseite: Das ist Kodierung, nicht Schutz. Über eine klare HTTP-Verbindung sind die Anmelde-Daten ein base64 -d entfernt von jedem auf dem Draht, also ist Basic Auth eine HTTPS-only-Gewohnheit. (Die modernen Alternativen - Bearer-Tokens, mTLS - wiederverwenden alle dieselbe Maschinerie: Einen String zusammensetzen, ihn kodieren, in einen Header legen. Base64 ist seitdem HTTP Header hat, der Weg, strukturierte Daten durch Text-Header zu schmuggeln.)

E-Mail und PEM: Wo der Umbruch zu Hause ist

E-Mail ist der Grund, warum Zeilen-Umbruch überhaupt existiert. SMTP begrenzt die Zeilenlänge, also deckelte MIME kodierte Zeilen auf 76 Zeichen (PEM, sein Ahn, auf 64), und jedes Mail-System hat diese Obergrenze seit dreißig Jahren eingehalten. Wenn Ihr C-Programm Base64 für einen E-Mail-Body oder einen Anhang produziert, ist der Umbruch keine optionale Kosmetik - eine nicht umgebrochene 200-kB-Zeile wird von Teilen der Mail-Infrastruktur abgelehnt oder vermurkst. Der Streaming-Encoder von OpenSSL gibt Ihnen umgebrochene Ausgabe umsonst (in seiner 64-Zeichen-Erblastlänge), und wenn Sie exakt 76 brauchen, ist das Umbruch-Wrappern eines One-Shot-Ergebnisses eine Fünf-Zeilen-Schleife:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  unsigned char enc[64];
  int n = EVP_EncodeBlock(enc, (const unsigned char *)"hello world", 11);
  for (int i = 0; i < n; i += 76) {
    int chunk = i + 76 < n ? i + 76 : n;
    printf("%.*s\r\n", chunk, (char *)enc + i);
  }
  return 0;
}

Achten Sie auf das \r\n: E-Mail will CRLF-Zeilenumbrüche, und wenn der kodierte Text einer von mehreren MIME-Teilen ist, folgt alles um den base64-Block herum denselben Regeln - Zeilenlängen, CRLF-Enden, keine Ausnahmen. PEM-Dateien (das Format der meisten Schlüssel und Zertifikate) verwenden dieselbe Idee mit 64-Zeichen-Zeilen zwischen -----BEGIN- und -----END-Markern, und die OpenSSL-Tools erwarten beim erneuten Speichern eines Schlüssels, diese Rüstung zu sehen - also wenn Ihr Programm PEM berührt, brechen Sie bei 64 um und behalten Sie die Labels. Überall anders - JSON, URLs, APIs, Datenbanken - gelten die Regeln des RFC, und Sie brechen überhaupt nicht um.

Werte durch Konfigs und Spalten schmuggeln

Der ruhige Anwendungsfall: Werte, die ein Text-Format kaputtmachen würden, werden als Base64 verpackt, damit sie es nicht tun. Ein Datenbank-DSN mit Semikolons, ein Passwort mit Anführungszeichen, ein Token mit einem Zeilenumbruch - die Ops-Person kodiert sie einmal, und die Konfigurationsdatei sieht die lästigen Zeichen nie:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  const char *dsn = "pg:host=db;password=qu\"ote";
  unsigned char enc[128];
  int n = EVP_EncodeBlock(enc, (const unsigned char *)dsn,
                          (int)strlen(dsn));
  printf("DB_DSN_B64=%.*s\n", n, (char *)enc);
  return 0;
}

Das Programm druckt die exakte Zeile, die in eine .env-Datei gepackt werden soll, und der C-Code, der sie später liest, ist ein getenv plus ein Decode. Drei ehrliche Vorbehalte, alle darüber, was das nicht ist. Es ist keine Verschlüsselung: Jeder, der die Konfigurationsdatei lesen kann, kann den Wert mit einem einzigen Aufruf dekodieren, also packen Sie nie ein Geheimnis als Base64 ein und nennen Sie es geschützt. Es ist kein Escaping: Wenn das Format erhaltene Struktur braucht, ist eine echte Kodierung (Percent-Encoding für URLs, JSON-Escaping für JSON) das richtige Werkzeug, und Base64 ist für die Werte, die diese Formate nicht ausdrücken können - die binären. Und es kostet Größe: Ein Wert, der in einer Datenbank-TEXT-Spalte als Base64 gespeichert ist, belegt etwa 33 Prozent mehr Platz als das Original, was für Tokens fein ist und für Dateispalten eine echte Zahl (das sind BLOB-Spalten dafür da).

Kodierung aus der Shell

Bevor Sie zu einem cc-Aufruf greifen, denken Sie daran, dass beide Standard-Tools kodieren, und schnell. coreutils ist das allgemeine Instrument: base64 kodiert standardmäßig mit 76-Zeichen-Umbruch, -w ändert die Spalte, und -w 0 deaktiviert den Umbruch komplett:

base64 photo.png > photo.b64
base64 -w 0 photo.png > photo-oneline.b64
cat note.txt | base64 -w 0

OpenSSls Tool ist derselbe Job mit TLS-Abstammungs-Verpackung: openssl base64 (der freundliche Alias von openssl enc -base64) bricht bei 64 Zeichen um, und -A schaltet es auf eine einzige Zeile:

openssl base64 < photo.png > photo.b64
openssl base64 -A < photo.png > photo-oneline.b64

Warum sollten Sie sich um den Umbruch-Unterschied kümmern? Weil die Standard-Ausgaben der zwei Tools nicht austauschbar sind, wenn ein nachgelagerter Parser Zeichen zählt - 76 pro Zeile versus 64 pro Zeile ist ein sichtbarer Unterschied in der Datei, und ein Parser, der Weißraum streift, kümmert sich nicht darum, während einer, der die Zeilenlänge validiert, sich absolut darum kümmert. Wenn Ihr C-Programm der Produzent ist und die Shell der Konsument (oder umgekehrt), einigen Sie sich zuerst auf den Umbruch. Eine Dialekt-Notiz für BSD-artige Systeme: Die Decode-Flagge dort war historisch -D, und ältere macOS-Releases erinnern sich noch daran; die Encode-Seite ist überall base64, und das ist die einzige Richtung, um die es in diesem Abschnitt ohnehin geht.

Große Teile im Stream

Eine mehrere Gigabyte große Datei in einem einzigen malloc zu kodieren ist ein Speicher-Problem, das Sie nicht brauchten. Der Streaming-Weg existiert genau dafür, und OpenSSls Block-Disziplin macht den Code fast trivial: Füttern Sie EVP_EncodeUpdate mit so viel, wie die Datei hergibt, lassen Sie den Rest jedes teilweisen 48-Byte-Blocks im Kontext halten, und schreiben Sie die 65 Ausgabe-Bytes jedes Blocks direkt in die Zieldatei. Der Peak-Speicher sind Ihre zwei Puffer - wenige Dutzend Kilobytes - egal wie groß die Datei ist:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
  if (ctx == NULL) {
    return 1;
  }
  EVP_EncodeInit(ctx);
  FILE *in = fopen("video.mp4", "rb");
  FILE *out = fopen("video.b64", "w");
  if (in == NULL || out == NULL) {
    return 1;
  }
  char inbuf[48 * 1024];            /* ein Vielfaches von 48: saubere Zeilen */
  unsigned char outbuf[1024 * 65 + 65];  /* 65 Ausgabe-Bytes pro 48-Byte-Block, plus Reserve */
  size_t got;
  while ((got = fread(inbuf, 1, sizeof(inbuf), in)) > 0) {
    int outl = 0;
    EVP_EncodeUpdate(ctx, outbuf, &outl,
        (const unsigned char *)inbuf, (int)got);
    fwrite(outbuf, 1, (size_t)outl, out);
  }
  unsigned char tail[66];
  int outl = 0;
  EVP_EncodeFinal(ctx, tail, &outl);
  fwrite(tail, 1, (size_t)outl, out);
  EVP_ENCODE_CTX_free(ctx);
  fclose(in);
  fclose(out);
  return 0;
}

Zwei Design-Details leisten in dieser Schleife Arbeit. Der Eingabe-Puffer ist ein Vielfaches von 48 Bytes, also reicht jeder Aufruf dem Encoder vollständige Blöcke, und jede Zeile, die er schreibt, ist eine fertige 64-Zeichen-Zeile; der finale Aufruf bricht dann den echten Tail um. Wenn Ihre Eingabe in beliebigen Größen kommt (ein Socket, eine langsame Platte), nimmt der Kontext die Fehlausrichtung trotzdem korrekt auf - die Wahl der Vielfachen von 48 geht um Ausgabe-Vorhersehbarkeit, nicht um Korrektheit. Das zweite Detail ist die Ausgabe-Puffer-Größe: 65 Bytes pro 48 Eingabe-Bytes plus die Reserve des letzten Blocks (66), was die umgebrochene Formel aus dem Mathematik-Abschnitt ist, pro Chunk angewendet. Fortschrittsmeldungen sind eine Zeile - zählen Sie die nach out geschriebenen Bytes gegen die Gesamtheit, die Sie aus der Dateigröße berechnet haben - und weil Kodierung die Daten vergrößert, wird die Ausgabedatei etwa 33 Prozent größer als die Eingabe landen: Rechnen Sie der Platte im Voraus ab.

Die scharfen Kanten der Kodierung

Die Fallen, gesammelt, alle C-förmig:

  • Off by one, in beide Richtungen. OpenSSL gibt die Länge ohne sein NUL zurück; Mbed TLS' Größenanfrage enthält Platz für das NUL; APR zählt das NUL in seine Längen hinein. Drei Bibliotheken, drei Buchhaltungs-Konventionen. Schreiben Sie die Buffer-Größen-Funktion einmal (der Mathematik-Abschnitt) und hören Sie auf, am Aufrufsort Arithmetik im Kopf zu machen.
  • Das NUL ist ein Byte, das Sie bezahlen müssen. Jeder Encoder in diesem Artikel will ein zusätzliches Byte in der Ausgabe für den Terminator, und jeder von Hand geschriebene Buffer-Append auch. Ein Puffer, der exakt auf encoded_chars(n) dimensioniert ist, ist ab dem Moment ein Byte zu kurz, in dem jemand will, dass printf("%s") funktioniert.
  • Kodieren kann nicht fehlschlagen, also dürfen Sie es nicht überlaufen lassen. Es gibt keinen Fehler-Code, der einen zu kleinen Puffer fängt - der Encoder schreibt fröhlich weiter, bis über das Ende hinaus. Das Fehlerschema der Base64-Kodierung in C ist ganz allein Ihres: Dimensionieren Sie richtig, oder es beschädigt Speicher ohne jegliche Diagnostik.
  • Doppel-Kodierung ist der klassische stille Bug. Ein Wert, der schon Base64 ist, nochmal durch den Encoder geschickt, erzeugt einen perfekt gültigen Base64-String, der zu einem Base64-String dekodiert statt zu den Daten. Das Symptom - "es dekodiert, aber zum falschen Ding" - kostet einen Nachmittag zu finden. Wenn ein Wert "vor-kodiert" ankommt, prüfen Sie, dass seine Länge ein Vielfaches von vier ist und nur Alphabetzeichen enthält, bevor Sie annehmen, es sei rohe Daten; wenn es kodiert ist, lassen Sie das Kodieren aus.
  • Das Plus-Zeichen in URLs. Standard-Alphabet-Ausgabe, in einen Query-String gesteckt, kommt an, wenn Ihr Server das Formular parst, mit dem + in ein Leerzeichen verwandelt - das + ist ein Leerzeichen in der Percent-/Form-Kodierung. Tokens und IDs, die in URLs reisen, wollen den URL-sicheren Dialekt, Ende der Diskussion.
  • Text-Modus auf der falschen Seite der Pipe. Eine binäre Datei im Text-Modus zu lesen kann Bytes übersetzen (auf einigen Plattformen) und Ihre Länge ändern; umgebrochenes Base64 mit der falschen Zeilenende-Konvention zu schreiben bricht Empfänger, die Zeichen zählen. rb für binäre Eingabe, explizites \r\n oder \n, wo eine Spezifikation eines verlangt, und lassen Sie nie die C-Runtime still Ihre Zeilenumbrüche entscheiden.
  • int-Overflow in der Größen-Mathematik. ((n + 2) / 3) * 4 in int-Arithmetik überläuft für Eingaben über grob 1,5 GB und erzeugt eine kleine positive "benötigte Größe" und einen Heap-Smash. Machen Sie die Mathematik in size_t (oder uint64_t), was auch der Grund ist, warum APRs int-basierte API eine 2-GB-Decke hat, die sich nicht umgehen lässt.
  • Umbruch, wo der Empfänger ihn nicht erwartet. RFC 4648 sagt: Keine Zeilenumbrüche, außer die umgebende Spezifikation bittet darum. Ein Zeilenumbruch in einem JSON-String-Wert ist ungültig; in einer URL ist er ein anderer Request. Umbruch für Mail, Umbruch für PEM, und nirgendwo sonst.

Die kurze Checkliste

Berechnen Sie die Buffer-Größe mit der Formel, nicht mit einer Schätzung, und behalten Sie eine einzige Größen-Funktion für die ganze Codebase. Halten Sie (Zeiger, Länge) zusammen, selbst wenn der Puffer NUL-terminiert ist, denn die Länge ist der Vertrag und das NUL ist eine Bequemlichkeit. Wählen Sie den Dialekt nach dem Kanal: Standard für JSON und Bodies, URL-sicher für URLs und Tokens, umgebrochen für Mail und Rüstung, nicht umgebrochen überall sonst. Verifizieren Sie die Magic-Bytes, bevor Sie einen MIME-Typ in einer Data-URI behaupten. Verwenden Sie Base64 nie als Verschlüsselung, als Ersatz für Percent-Encoding oder als Versteck für ein Geheimnis - es ist eine Box, kein Schloss. Und wenn die Daten groß sind, streamen Sie sie: Die block-basierten Encoder wurden genau dafür entworfen, und konstanter Speicher ist der ganze Punkt.

Geschichte: Wie das Packen standardisiert wurde

Die Geschichte des Encoders ist die Geschichte der Zeilenlängen. Das erste Base64 war ein C-Programm aus dem frühen 1990ern. Privacy-Enhanced Mail (RFC 1421, 1993) musste Binäres durch 7-Bit-Mail tragen, und seine Autoren wählten sechs Bits pro Zeichen in 64-Zeichen-Zeilen - die 64 ist ein Relikt der SMTP-Zeilenlängen-Toleranz, und der C-Code machte das Packen Tabellen-Lookup um Tabellen-Lookup. Als MIME dasselbe Alphabet für das Web standardisierte (RFC 1521 im Jahr 1993, RFC 2045 im Jahr 1996), lockerte es die Zeile auf 76 Zeichen, und die Welt trug zwei Gewohnheiten - 64 und 76 -, die beide behaupteten, "die" Base64-Zeilenlänge zu sein. Encoder passten sich an: Der Streaming-Weg von OpenSSL behielt 64 (seine PEM-Erblast), das coreutils-Tool wählte 76 (seine MIME-Erblast), und die zwei Tools auf derselben Maschine sind sich immer noch nicht einig, wo die Zeilenumbrüche hingehören. Der Standard nahm endlich 2006 eine Position ein: RFC 4648 sagte, Implementierungen dürften gar keine Zeilenumbrüche hinzufügen, es sei denn, die verweisende Spezifikation weist sie ausdrücklich dazu an - deshalb ist die Standardausgabe jeder Bibliothek in diesem Artikel nicht umgebrochen, und deshalb ist der Umbruch jetzt ein Opt-in-Feature für E-Mail und Rüstung. Das Alphabet selbst, die Padding-Regeln und die Kanonizitäts-Regel "die Pad-Bits müssen null sein" stammen aus den früheren PEM- und MIME-RFCs, und 4648 stellt sie als die kanonischen Regeln für die Familie neu auf. Und Abschnitt 11 des RFC verweist auf eine Referenzimplementierung - ein ISO-C99-Programm, extern gehostet, weil der Code selbst "aus prozeduralen Gründen nicht in diesem RFC enthalten werden konnte" - eine weitere Erinnerung daran, dass C in diesem Format kein Bürger zweiter Klasse ist. Die C-Standardbibliothek hat ihrerseits nie aufgeholt: C89 erstarrte 1990, bevor irgendetwas davon existierte, und C23 im Jahr 2024 liefert immer noch ohne eine Base64-Funktion. Also sind die Bibliotheken, die Sie linked, der Standard, und die Wahl zwischen ihnen ist eine kleine, aber echte Design-Entscheidung - worum es in diesem Artikel gegangen ist.

Seltsame kleine Fakten

Einige Fakten, die einfach Spaß machen, alle über die Pack-Seite in C:

  • Die "64" ist die Basis: Jedes Ausgabezeichen ist sechs Bits, und 2 hoch 6 ist 64. Das Format benennt sein Alphabet so, wie C seine Ganzzahlen benennt - nach dem, was die Zahl tatsächlich ist.
  • Der 48-Byte-Block von OpenSSls Streaming-Encoder ist keine beliebige Buchhaltung: 48 Eingabe-Bytes sind exakt 16 Gruppen von 3, und 64 Ausgabe-Zeichen sind exakt 16 Gruppen von 4. Beide Zahlen sind Vielfache von 16, und das ist die Art von Rundheit, die Hardware und Cache-Zeilen glücklich macht - oder zumindest die Menschen, die den Code lesen.
  • Ein Eingabe-Byte kodiert zu vier Zeichen, zwei davon sind =. Der kleinste mögliche nicht-leere Payload ist zu 50 Prozent Padding - die verschwendendste Kodierung im Format, und die, die jede Test-Suite verwendet, weil sie so leicht falsch zu schreiben ist.
  • Mbed TLS ist der einzige Encoder in diesem Artikel, der seine Lookups in konstanter Zeit macht, denn die Leute, die Embedded-Krypto schreiben, vertrauen Tabellen-Indexing mit variabler Laufzeit nicht, nicht einmal in einem Codec, der kein Chiffre ist. Die Paranoia überträgt sich.
  • Die Kanonizitäts-Regel der Kodierung - ungenutzte Pad-Bits müssen null sein - klingt trivial, bis Sie erfahren, dass ein Verstoß dagegen bedeutet, dass zwei verschiedene Strings zu denselben Bytes dekodiert werden können, was jede "ist dieser String die Kodierung dieser Datei?"-Prüfung bricht, die es gibt. Ihre Encoder halten sich alle daran; deshalb ist base64 eine hash-stabile Repräsentation und kann in einem Content-Store für einen Dateinamen stehen.
  • OpenSSls EVP_EncodeBlock ist eine der seltenen C-Funktionen, deren Rückgabewert, ihre eigene Ausgabe und ihr NUL-Terminator sich alle einig sind: Sie schreibt n Zeichen, ein NUL und gibt n zurück. In einer Sprache, die berühmt ist für Off-by-one, ist das ein Moment der Ruhe.
  • APR-Util ist der einzige Encoder hier, der fragt, was EBCDIC bedeutet, denn Apache läuft immer noch auf Maschinen, auf denen die Buchstaben in einer anderen Reihenfolge stehen als bei ASCII. Auf diesen Maschinen beinhaltet das "Kodieren" eines Strings erst das stille Neu-Sortieren seines Alphabets.
  • Die leere Eingabe kodiert in jeder Bibliothek zum leeren String, ohne Pads und ohne Zeilenumbrüche. Das Identitätselement des Formats, in allen vier vorhanden und korrekt, was es zum billigsten Unit-Test macht, den Sie je schreiben werden.

Der Wechsel auf die Dekodierer-Seite

So war das die Pack-Seite: die Mathematik, die vier Encoder, die Dialekte und die Orte, an die die Bytes gehen. Sie ist die ruhige Hälfte des Jobs, denn Kodierung hat keine ungültige Eingabe und keinen Dekodierer, der mit Ihnen uneins wird. Die andere Richtung - auf die Base64 der Außenwelt zu treffen und die Bytes zurückzubekommen - ist der Ort, an dem sich der Schmerz konzentriert: null-aufgefüllte Tails, stille Abbrüche, strenge versus nachsichtige Alphabete und eine Kommandozeile, die nachgestellte Zeilenumbrüche frisst. Das Base64-Dekodieren in C wird im verwandten Artikel, der von dieser Seite verlinkt ist, eingehend behandelt, und er ist der natürliche Begleiter zu diesem hier: Der Encoder schreibt die Box, der Dekodierer öffnet sie, und zusammen haben Sie damit jeden Base64-Job, den ein C-Programm je treffen wird.

Zuletzt aktualisiert: 2026-09-08

Verwandter Artikel: Base64-Dekodierung in C: Ein vollständiger Leitfaden