Base64-Kodierung in C: Ein vollständiger Leitfaden
Sie haben Bytes. Vielleicht sind es ein JPEG, von der Platte gelesen, vielleicht ein Token, vielleicht ein Passwort, das ein Client gleich übermitteln wird, vielleicht die rohen Bytes einer Datei, die eine Pipeline in einem JSON-Feld erwartet. Und irgendwo weiter vorne auf dem Weg gibt es einen Kanal, der nur Text spricht: einen JSON-String, eine URL, einen E-Mail-Körper, eine Konfigurationsdatei, eine Datenbank-Spalte, die so tut, als wäre sie Text. Genau dort kommt Base64-Kodierung ins Spiel: Sie schreibt je drei rohe Bytes um in vier Zeichen aus einem 64-Buchstaben-Alphabet, so dass das Ergebnis reines ASCII ist, das jede Text-Pipeline der Welt ü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: Encodieren vergrößert Ihre Daten. Drei Bytes werden zu vier Zeichen, also verlässt jedes Payload Ihr Programm etwa ein Drittel größer, plus ein bisschen mehr, immer wenn Zeilenumbrüche hinzukommen. Das ist die Steuer, und es gibt keinen Weg daran vorbei - aber in C hat die Steuer einen Posten, denn Sie allozieren den Ausgabe-Buffer selbst, und der Buffer muss exakt groß genug sein. Rechnen Sie einmal richtig, und jeder Encoder in diesem Artikel wird vorhersehbar: keine Overflows, keine Underflows, kein Raten, wo das nächste Byte landen wird. Dann gibt es die Werkzeugkiste zur Auswahl - OpenSSL, Mbed TLS, APR-Util, GLib - und die Wahl zählt, denn jede umbricht anders, terminiert anders und feuert Fehler anders.
Vier Encoder, vier Persönlichkeiten
Alle vier Bibliotheken kodieren das Standardalphabet korrekt und identisch - dieselben Bytes rein, dieselben Zeichen raus, immer. Die Unterschiede liegen in der Verpackung, und die Verpackung ist es, in der Interoperabilitäts-Bugs stecken. Hier ist die Landschaft:
| Bibliothek | Header | Ausgabe-Stil | Fehlermodus |
|---|---|---|---|
| OpenSSL (libcrypto) | <openssl/evp.h> |
Keine Zeilenumbrüche; schreibt einen NUL-Terminator | Praktisch keiner (nur Allozierung) |
| Mbed TLS | <mbedtls/base64.h> |
Keine Zeilenumbrüche; NUL-terminiert | Buffer-zu-klein-Code mit benötigter Größe |
| APR-Util | <apr-1.0/apr_base64.h> |
Keine Zeilenumbrüche; hängt einen NUL an | Keiner - vertrauen Sie auf Ihre Buffer-Größe |
| GLib | <glib.h> |
Keine Zeilenumbrüche; NUL-terminiert, Heap-alloziert | Gibt NULL zurück (nur Allozierung) |
Achten Sie darauf, was in der Tabelle fehlt: Keines von ihnen umbricht Zeilen standardmäßig. Das ist gewollt - RFC 4648 sagt, dass Implementierungen keine Zeilenumbrüche hinzufügen dürfen, es sei denn, die umgebende Spezifikation verlangt es ausdrücklich - und das ist eine Erleichterung, denn ein versehentlicher Zeilenumbruch in einem JSON-String oder einer URL ist ein Fehler, kein Feature. Umbruch existiert für E-Mail und PEM, und wenn Sie ihn brauchen, bekommen Sie ihn vom Streaming-Pfad von OpenSSL oder Sie umbrechen selbst in fünf Zeilen (der E-Mail-Abschnitt zeigt beide). Für die Wahl der Bibliothek: Verwenden Sie OpenSSL, wenn Sie es bereits linken, Mbed TLS für eingebettete Builds, wo über jedes Kilobyte gestritten wird, APR-Util im Apache-Ökosystem, und GLib, wenn der Rest Ihres Programms bereits 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.
Rechnen, bevor Sie allozieren
Vor jedem Code die Arithmetik, denn C rettet Sie nicht vor einem zu kleinen Buffer. Jedes drei Eingabe-Bytes produzieren genau vier Ausgabe-Zeichen. Wenn die Eingabelänge kein Vielfaches von drei ist, produziert 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 werden 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 nach der Größe fragen), also braucht Ihr Buffer ein zusätzliches Byte: encoded_chars(n) + 1. Zweitens: Wenn Sie umgebrochene Ausgabe wollen, addieren Sie einen Zeilenumbruch pro Zeile: OpenSSLs Streaming-Encoder gibt für jedes 48 Eingabe-Bytes eine 64-Zeichen-Zeile aus, also ist die umgebrochene Länge encoded_chars(n) + (n + 47) / 48. Verifizieren 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; das 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 Wasserhahn
OpenSSLs One-Shot-Funktion ist das Arbeitstier, und sie ist die freundlichste der ganzen Gruppe:
#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 einen NUL danach an, und gibt die Länge ohne den NUL zurück - also ist %s-Ausgabe sicher und die Länge ist verfügbar, wenn Sie sie brauchen. Der Ausgabe-Buffer muss encoded_chars(n) + 1 Bytes fassen. Es gibt keinen Fehlerpfad, den Sie abfangen müssen: Encodieren kann nicht fehlschlagen, denn jedes Byte ist legale Eingabe, und die Funktion hat kein Eingabevalidierungs-Konzept, an dem sie scheitern könnte. Der einzige Weg, auf dem es schiefgehen kann, ist, dass Sie ihr einen zu kleinen Buffer geben, und der Mathematik-Abschnitt ist das Gegenmittel.
Das Streaming-Paar ist für den Fall, dass die Daten groß sind oder in Stücken ankommen. EVP_EncodeUpdate verarbeitet Eingaben in 48-Byte-Blöcken und schreibt 64 Zeichen plus Zeilenumbruch (65 Bytes) pro vollständigem Block, und hält jeden Rest im Kontext, bis mehr Daten oder der finale Aufruf kommen:
#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 + Puffer */
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, gemacht zu Wirklichkeit. Zwei praktische Hinweise. Der Versionshinweis: Seit OpenSSL 1.1.0 (2016) ist der Kontexttyp opak, also allozieren Sie mit EVP_ENCODE_CTX_new() und freigeben Sie mit EVP_ENCODE_CTX_free(); das ältere Stack-Muster EVP_ENCODE_CTX ctx;, das Sie in Tutorials finden, die OpenSSL 1.0.2 oder älter im Visier haben, kompiliert nicht gegen moderne Header, OpenSSL 3.x eingeschlossen. Und der Design-Hinweis: Weil nur vollständige 48-Byte-Blöcke aus EVP_EncodeUpdate ausgegeben werden, füttert die sauberste Pipeline sie 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 willkürlichen Größen ankommt (ein Netzwerk-Read), übernimmt der Kontext die Ausrichtung trotzdem für Sie; die Vielfache-von-48-Gewohnheit ist nur das, was die Ausgabe vorhersehbar macht.
Mbed TLS: Fragen, dann Encodieren, String bekommen
Mbed TLSs Encodieren hat den saubersten Vertrag der Gruppe, gebaut um eine Größenabfrage, 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 Meisterschule für eine freundliche API. Die Größenabfrage meldet needed als die kodierten Zeichen plus eins für den NUL - für "Mane" sind das 8 plus 1, also 9 - und sie signalisiert sich selbst mit dem "Buffer zu klein"-Code (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL, das ist -0x002A), denn ein NULL-Ziel ist per Definition zu klein. Der echte Aufruf schreibt dann die Zeichen und den NUL, und *olen kommt als 8 zurück - die Länge ohne den Terminator - so ist der Buffer schon ein druckbarer C-String. Wenn Sie ihm einen Buffer geben, dem ein Byte fehlt, bekommen Sie denselben "zu klein"-Code zurück, mit der benötigten Größe in *olen, also sagt Ihnen der Fehlschlag genau, wie viel Sie verfehlt haben. Noch ein Hinweis: Die Bibliothek macht ihre Tabellen-Lookups über konstantzeitige Helfer, eine kleine Note an Sorgfalt, die Sie in den meisten Encodern nicht sehen werden.
APR-Util und GLib: Die anderen zwei
APR-Utils Encoder 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 apr_base64_encode_len() und der Rückgabewert beide den NUL, also ist n eins mehr als die Zeichenzahl - eine Buchführungsdifferenz zu OpenSSL und Mbed TLS, die in so manchem Codebase Off-by-one-Bugs produziert hat. Dasselbe 32-Bit-Längenlimit gilt: Für Werte nahe oder über 2 GB ist das nicht das Tool. 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 sonst passiert, und ein No-op-Unterschied überall sonst. Das Paar, die binäre 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.
GLibs Encoder 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-Annotation auf dem Prototyp sagt es den statischen Analyse-Tools. Wenn Sie Zeilenumbrüche wollen, ist das inkrementelle Paar das Tool: g_base64_encode_step() nimmt eine Zustands-Ganzzahl und eine break_lines-Flag und teilt Ihnen mit, wie viele Ausgabe-Bytes sie geschrieben hat, und g_base64_encode_close() macht die letzte partielle Gruppe fertig. Das ist dieselbe Zustandsmaschine wie bei OpenSSLs Streaming-Paar, nur mit GLibs Parameter-Stil.
Handgemachtes URL-sicheres Base64
Die vier Bibliotheken oben sprechen alle das Standardalphabet: A-Z, a-z, 0-9, Plus und Slash. Das Web hingegen spricht zunehmend den zweiten Dialekt aus RFC 4648 Abschnitt 5, genannt base64url: dieselbe Kodierung mit + getauscht durch - und / getauscht durch _, und das abschließende =-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 in URLs beide gefährlich, während - und _ unreservierte Zeichen sind, die problemlos durchkommen. Da keine C-Bibliothek diesen Dialekt nativ ausstößt, bauen Sie ihn selbst - und es sind zwei kleine Änderungen, denn Sie haben bereits einen Standardalphabet-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 absichtlich weggelassen */
}
Die Nutzung ist zwei Schritte - standard encodieren, 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 Vorsichtsregeln. Die Schleife stoppt beim ersten =, und das ist es, was das Padding wegfallen lässt - "korrigieren" Sie das nicht, das Weglassen des Paddings ist der Punkt (der Empfänger, der es braucht, kann es aus der Länge wieder anfügen). Und bemessen Sie out auf die volle kodierte Länge, nicht weniger: Die Übersetzung ist Zeichen für Zeichen bis zu den Pads, also ist die Kapazität, die Sie bereits für die Standardform allozierten, genau richtig. Ein ehrlicher Hinweis für die Interoperabilität: Wenn Ihre Daten zufällig keine Bytes enthalten, die auf + oder / mappen, sind die Standard- und URL-sichere Form identisch, und nichts wird je über ein Verwechseln klagen - der Bug erscheint nur, wenn die Daten schließlich eines davon enthalten. Behandeln Sie den Dialekt als Eigenschaft des Kanals (URLs, Tokens), nicht der Daten.
Text und Zeichensätze: UTF-8 ist nur Bytes
Eine Frage, die C-Neulinge überrascht: Was passiert mit akzentuiertem Text, Emoji und CJK-Zeichen? Die Antwort ist die befreieste Tatsache in diesem Artikel - nichts muss passieren. Base64 arbeitet auf Bytes, und C ist eine Sprache von Bytes. Wenn Ihr Text UTF-8 ist (was er 2026 wahrscheinlich ist), ist die UTF-8-Kodierung von "café" fünf Bytes - 63 61 66 c3 a9 - und Base64 kodiert diese fünf Bytes genau wie irgendwelche anderen fünf Bytes und erzeugt Y2Fmw6k=. Kein Charset-Parameter, kein BOM, kein Umwandlungsschritt, kein Bibliothek-Aufruf. Der Codec weiß nicht und kümmert sich nicht, 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 weite Zeichen angekommen sind, müssen Sie sie zuerst in eine Byte-Sequenz umwandeln (auf Linux, UTF-8, via wcstombs() oder Ihrer Locale-Maschinerie), bevor Sie encodieren - Base64 eines wchar_t-Arrays ist die Kodierung einer internen Darstellung, nicht des Textes, und sie wird zwischen Plattformen unterschiedlich sein. Die zweite ist die Quellen-Kodierung: Der String-Literal in Ihrer C-Datei ist in der Kodierung der Quelldatei kodiert (UTF-8 in jedem modernen Projekt), also funktioniert "café" direkt zu schreiben, solange die Datei wirklich UTF-8 ist und Ihr Compiler es weiß (was er in modernen Toolchains standardmäßig tut). Kodieren Sie die Bytes, die Sie senden wollen, und lassen Sie den Empfänger damit fertig werden, mit dem, was die Bytes bedeuten.
Bilder: Vom Buffer zum String
Der häufigste "echte" Encodierungs-Job im Web-C: Eine Binärdatei - ein JPEG, ein PNG, ein Icon - muss durch einen Text-Kanal reisen, also wird sie zu einem Base64-String. Das Rezept lautet: Datei in einen Buffer lesen, die Ausgabe mit der Formel bemessen, encodieren, und weitergehen. Die Datei-Lese-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;
}
Anmerkungen: rb zum binären Lesen - auf keiner Plattform verhandelbar, denn im Textmodus können Bytes übersetzt werden und got verändern; das fseek/ftell-Paar für die Größenbestimmung (für Pipes und Sockets ohne Seek stattdessen in einen wachsenden Buffer lesen); und got statt size für das Encodieren, denn ein kurzer Read ist eine echte Möglichkeit. Ein 1-MB-Bild wird etwa 1,33 MB Text - das ist die Steuer, im Voraus in Rechnung gestellt, und genau 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, eine Sicherung einer Binärdatei in einem text-sicheren Store, einen Anhang für einen Mailer. Dieselbe Mathematik, anderer Writer. Die Gewohnheit, die sich zu übernehmen lohnt, ist, die Text-Ausgabe mit expliziten Zeilenumbrüchen in einer Länge zu schreiben, die die Empfangsseite 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 Decoder, der Leerzeichen überspringt (jeder ernsthafte tut das), wird sich um die Zeilenlänge überhaupt nicht kümmern, deshalb ist der Empfänger die Anlaufstelle, nicht Ihr Geschmack. Halten Sie die Ausgabedatei auf der Schreibseite im Textmodus (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 ein Zeilenumbruch, nichts anderes. Dieses Versprechen, nicht die Bytes, macht eine .b64-Datei zu einem Format.
Data-URIs: Einbetten für den Ernstfall
Data-URIs (RFC 2397) sind die andere Seite des Lieblingseintreffs aus dem Decode-Artikel: Statt data:image/png;base64,... zu empfangen, bauen Sie einen. Die Form ist data:, der Medientyp, ;base64, ein Komma, das Payload - und das Bauen in C ist ein snprintf nach dem Encodieren:
#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-Hinweise. Der Medientyp, den Sie in den URI schreiben, ist eine Behauptung, für die Sie verantwortlich sind - sniffen Sie zuerst die Magic-Bytes der echten Datei, sonst wird ein data:image/png, das ein JPEG trägt, jeden Konsumenten auf eine andere Weise verwirren. Die ;base64-Flag ist Pflicht, wenn das Payload Base64 ist; lassen Sie sie weg, und das Payload muss stattdessen percent-kodierter Text sein, was ein ganz anderes Format ist. Und die eigene Richtlinie des RFC ist, dass Data-URIs für kurze Werte sind: Ein 5-MB-Logo inline in einer HTML-Seite einzubetten funktioniert, aber es ist ein Design-Gestank, den eine echte Asset-URL beheben würde. Dieselbe Konstruktion taucht ständig in JSON-APIs auf, wo ein Client einen Avatar im selben Request wie die Formular-Daten will - encodieren, voranstellen, senden.
HTTP und JSON: Payloads, die überleben
Der größte moderne Grund, in C zu encodieren, 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 Literal-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 Alphabet-Zeichen plus, im Standarddialekt, =, und keines davon ist ein Anführungszeichen oder ein Backslash. Die Binärdatei geht als String rein, und sie kommt genau so wieder raus, wie sie 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, ohne dass irgendwo Escaping-Maschinerie zum Einsatz kommt, und die %.*s-Präzision hält die Länge exakt, selbst wenn Sie einmal auf einen Encoder umsteigen, der nicht NUL-terminiert. Der ehrliche Preis ist die Größe: Jedes Byte, das Sie als JSON-String versenden, kostet Sie 4/3 Byte Luft plus den Feldnamen und die Anführungszeichen, also wird aus einer 10-KB-Binärdatei 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 Tool für diesen Job. Gleichfalls einen Satz wert: + und / des Standardalphabets 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 der JSON Web Token. Ein kompakter JWT gemäß RFC 7519 ist drei base64url-kodierte Teile, verbunden durch Punkte - Header, Payload, Signatur - und eines zu bauen ist eine angenehme Übung, denn jedes Stück ist eine Funktion, die Sie bereits haben: standard encodieren, nach URL-sicher ü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 Signierungs-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 Standardalphabet-Form, und die Verifizierung des Empfängers schlägt fehl, was ein Bug ist, der kompiliert, läuft und wie ein Key-Mismatch aussieht. Zweitens: Header und Payload sind klares JSON in einer Base64-Umhüllung: Jeder kann sie lesen, und das ist das Design. Ein Token ist ein signierter Zettel, kein versiegelter Briefumschlag - also stecken Sie nichts hinein, dessen Lesen Sie einem abgreifenden Nutzer verübeln würden, und stecken Sie niemals, je ein Passwort in ein JWT-Payload "weil es encodiert ist". Der Base64-Teil dieses Jobs ist klein und langweilig, und das ist das höchste Lob, das Sie einer JWT-Implementation erweisen können.
HTTP Basic Auth: Den Token bauen
Der älteste Authentifizierungs-Header ist auch der einfachste Base64-Job: username:password, im Standardalphabet kodiert, nach dem Wort Basic. Das Bauen in C sind zwei Zeilen, und die einzige Feinheit ist, dass das Passwort einen Doppelpunkt enthalten darf (und das Teilen auf der Empfangsseite beim ersten Doppelpunkt passieren muss):
#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 Sende-Seite: Das ist Kodierung, nicht Schutz. Über eine Klartext-HTTP-Verbindung ist das Credential für jeden auf der Leitung nur ein base64 -d entfernt, also ist Basic-Auth eine HTTPS-allein-Gewohnheit. (Die modernen Alternativen - Bearer-Tokens, mTLS - wiederverwenden alle dieselbe Maschinerie: String zusammensetzen, encodieren, in einen Header stecken. Base64 ist seitdem, als HTTP Header hatte, der Weg, strukturierte Daten durch Text-Header zu schmuggeln.)
E-Mail und PEM: Wo das Umbruch-Verhalten lebt
E-Mail ist der Grund, warum Zeilenumbruch überhaupt existiert. SMTP begrenzt die Zeilenlänge, also deckelte MIME kodierte Zeilen bei 76 Zeichen (PEM, sein Vorfahre, bei 64), und jedes Mail-System hat diese Deckelung dreißig Jahre lang befolgt. Wenn Ihr C-Programm Base64 für einen E-Mail-Körper oder einen Anhang produziert, ist der Umbruch keine optionale Kosmetik - eine nicht umgebrochene 200-KB-Zeile wird von Teilen der Mail-Infrastruktur abgelehnt oder verunstaltet. OpenSSLs Streaming-Encoder gibt Ihnen umgebrochene Ausgabe umsonst (in seiner 64-Zeichen-Erbschaftslänge), und wenn Sie exakt 76 brauchen, ist der Umbruch eines One-Shot-Ergebnisses eine Fünfzeilen-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 Keys und Zertifikate) verwenden dieselbe Idee mit 64-Zeichen-Zeilen zwischen -----BEGIN und -----END-Markern, und OpenSSLs Tools erwarten, diese Rüstung zu sehen, wenn Sie einen Key neu speichern - also, wenn Ihr Programm PEM berührt, umbrechen Sie bei 64 und behalten Sie die Labels. Überall anders - JSON, URLs, APIs, Datenbanken - gilt die RFC-Regel, und Sie umbrechen gar nicht.
Werte durch Konfigurationen und Spalten schmuggeln
Der leise Anwendungsfall: Werte, die ein Textformat brechen würden, werden als Base64 verpackt, damit sie es nicht tun. Eine Datenbank-DSN mit Semikolons, ein Passwort mit Anführungszeichen, ein Token mit einem Zeilenumbruch - die Ops-Person encodiert sie einmal, und die Konfigurationsdatei sieht die Ärger-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 Sie in eine .env-Datei kopieren, und der C-Code, der sie später liest, ist ein getenv plus ein Decode. Drei ehrliche Einschränkungen, alle darüber, was das hier nicht ist. Es ist keine Verschlüsselung: Jeder, der die Konfigurationsdatei lesen kann, kann den Wert mit einem einzigen Aufruf dekodieren, also verpacken Sie nie ein Secret als Base64 und nennen Sie es geschützt. Es ist kein Escaping: Wenn das Format Struktur bewahrt haben will, ist eine echte Kodierung (Percent-Encoding für URLs, JSON-Escaping für JSON) das richtige Tool, 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 als Base64 in einer Datenbank-TEXT-Spalte gespeichert ist, belegt etwa 33 Prozent mehr Platz als das Original, was für Tokens in Ordnung ist, für Datei-Spalten aber eine echte Größe ist (dafür sind BLOB-Spalten da).
Encodieren aus der Shell
Bevor Sie nach einem cc-Aufruf greifen, denken Sie daran, dass beide Standard-Tools encodieren, und schnell. coreutils ist das allgemeine Instrument: base64 encodiert 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-Astverwandtschaft-Verpackung: openssl base64 (der freundliche Alias für openssl enc -base64) umbricht bei 64 Zeichen, und -A schaltet es auf eine einzelne Zeile:
openssl base64 < photo.png > photo.b64
openssl base64 -A < photo.png > photo-oneline.b64
Wem kümmert der Umbruch-Unterschied? Weil die Standard-Ausgaben der beiden Tools nicht austauschbar sind, wenn ein nachgelagerter Parser Zeichen zählt - 76 pro Zeile gegenüber 64 pro Zeile ist ein sichtbarer Unterschied in der Datei, und ein Parser, der Leerzeichen streicht, kümmert sich nicht, während einer, der die Zeilenlänge validiert, es absolut tut. Wenn Ihr C-Programm der Produzent und die Shell der Konsument ist (oder umgekehrt), einigen Sie sich zuerst auf den Umbruch. Ein Dialekt-Hinweis für BSD-artige Systeme: Die Decode-Flag dort war historisch -D, und ältere macOS-Releases erinnern sich noch daran; die Encode-Seite ist überall base64, und das ist ohnehin die einzige Richtung, um die es in diesem Abschnitt geht.
Das Große streamen
Eine Datei mit mehreren Gigabytes in einem einzigen malloc zu encodieren ist ein Speicherproblem, das Sie nicht brauchten. Der Streaming-Pfad existiert genau dafür, und OpenSSLs Blockdisziplin macht den Code fast trivial: Füttern Sie EVP_EncodeUpdate so viel, wie die Datei gibt, lassen Sie den Rest jedes partiellen 48-Byte-Blocks im Kontext halten, und schreiben Sie die 65 Ausgabe-Bytes jedes Blocks direkt in die Zieldatei. Der Spitzen-Speicher sind Ihre zwei Buffer - einige 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 arbeiten in dieser Schleife. Der Eingabe-Buffer ist ein Vielfaches von 48 Bytes, also gibt jeder Aufruf dem Encoder vollständige Blöcke und jede Zeile, die er schreibt, ist eine fertige 64-Zeichen-Zeile; der finale Aufruf umbricht dann den echten Tail. Wenn Ihre Eingabe in willkürlichen Größen kommt (ein Socket, eine langsame Platte), absorbiert der Kontext die Fehlausrichtung trotzdem korrekt - die Vielfache-von-48-Wahl geht um Ausgabevorhersehbarkeit, nicht um Korrektheit. Das zweite Detail ist die Größe des Ausgabe-Buffers: 65 Bytes pro 48 Eingabe-Bytes plus die Reserve des finalen Blocks (66) - die umgebrochene Formel aus dem Mathematik-Abschnitt, pro Chunk angewendet. Fortschrittsanzeige ist eine Zeile - zählen Sie die Bytes, die nach out geschrieben wurden, gegen die Gesamtsumme, die Sie aus der Dateigröße berechnet haben - und weil Encodieren die Daten vergrößert, wird die Ausgabedatei etwa 33 Prozent größer als die Eingabe landen: Rechnen Sie die Platte im Voraus ab.
Die scharfen Kanten des Encodings
Die Fallen, gesammelt, alle C-geformt:
- Eins daneben, in beide Richtungen. OpenSSL gibt die Länge ohne seinen NUL zurück; Mbed TLSs Größenabfrage enthält Platz für den NUL; APR zählt den NUL in seinen Längen. Drei Bibliotheken, drei Buchführungs-Konventionen. Schreiben Sie die Buffer-Größen-Funktion einmal (der Mathematik-Abschnitt) und hören Sie auf, am Aufrufsort im Kopf zu rechnen.
- Der 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 Buffer, der exakt auf
encoded_chars(n)bemessen ist, hat in dem Moment ein Byte zu wenig, in dem jemandprintf("%s")arbeiten will. - Encodieren kann nicht fehlschlagen, also dürfen Sie es nicht überlaufen lassen. Es gibt keinen Fehlercode, der einen zu kleinen Buffer fängt - der Encoder schreibt gern bis über das Ende hinaus. Das Fehlerschema von Base64-Encoding in C ist ganz Ihres: Bemessen Sie richtig, oder es beschädigt Speicher ohne Diagnostics.
- Doppel-Encodierung ist der klassische stille Bug. Ein Wert, der bereits Base64 ist, durch den Encoder nochmal gejagt, produziert einen perfekt gültigen Base64-String, der zu einem Base64-String dekodiert statt zu den Daten. Das Symptom - "es dekodiert, aber zum Falschen" - kostet einen Nachmittag zu finden. Wenn ein Wert "vor-kodiert" ankommt, verifizieren Sie, dass seine Länge ein Vielfaches von vier ist und nur Alphabetzeichen enthält, bevor Sie annehmen, es sei Rohdaten; wenn es kodiert ist, lassen Sie das Encodieren aus.
- Das Plus-Zeichen in URLs. Standardalphabet-Ausgabe, in einen Query-String gestellt, kommt mit dem
+umgewandelt in ein Leerzeichen an, bis der Server das Formular parst - das+ist ein Leerzeichen in Percent-/Form-Encoding. Tokens und IDs, die in URLs reisen, wollen den URL-sicheren Dialekt, Punkt. - Textmodus auf der falschen Seite der Pipe. Eine Binärdatei im Textmodus zu lesen kann Bytes übersetzen (auf manchen Plattformen) und Ihre Länge verändern; umgebrochenes Base64 mit der falschen Zeilenumbruch-Konvention zu schreiben bricht Empfänger, die Zeichen zählen.
rbfür binäre Eingabe, explizites\r\noder\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) * 4inint-Arithmetik überläuft für Eingaben über etwa 1,5 GB und produziert eine kleine positive "benötigte Größe" und einen Heap-Smash. Rechnen Sie insize_t(oderuint64_t), was auch der Grund ist, warum APRs int-basierte API eine 2-GB-Obergrenze hat, die Sie nicht wegingenieuren können. - Umbruch, wo der Empfänger ihn nicht erwartet. RFC 4648 sagt: keine Zeilenumbrüche, es sei denn, die umgebende Spezifikation verlangt sie. 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 das gesamte Codebase. Halten Sie (Zeiger, Länge) zusammen, selbst wenn der Buffer NUL-terminiert ist, denn die Länge ist der Vertrag und der NUL ist eine Bequemlichkeit. Wählen Sie den Dialekt nach dem Kanal: Standard für JSON und Körper, 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 in einem Data-URI einen MIME-Typ behaupten. Verwenden Sie Base64 nie als Verschlüsselung, als Ersatz für Percent-Encoding oder als Ort, um ein Secret zu verstecken - es ist eine Box, kein Schloss. Und wenn die Daten groß sind, streamen Sie sie: Die blockbasierten Encoder wurden genau dafür entworfen, und konstanter Speicher ist der ganze Punkt.
Geschichte: Wie das Verpacken standardisiert wurde
Die Geschichte des Encoders ist die Geschichte der Zeilenlängen. Das erste Base64 war ein C-Programm aus den frühen 1990ern. Privacy-Enhanced Mail (RFC 1421, 1993) musste Binärdaten durch 7-Bit-Mail tragen, und seine Autoren wählten sechs Bits pro Zeichen in 64-Zeichen-Zeilen - die 64 ist ein Relikt von SMTPs Zeilenlängen-Toleranz, und der C-Code machte das Verpacken 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 beanspruchten, die Base64-Zeilenlänge zu sein. Die Encoder passten sich an: OpenSSLs Streaming-Pfad behielt 64 (sein PEM-Erbe), coreutils' Tool wählte 76 (sein MIME-Erbe), und die zwei Tools auf derselben Maschine streiten immer noch darüber, wo die Zeilenumbrüche hingehören. Der Standard nahm schließlich 2006 Position: RFC 4648 sagte, Implementierungen müssten gar keine Zeilenumbrüche hinzufügen, es sei denn, die verweisende Spezifikation weist sie explizit dazu an, und deshalb liefert jede Bibliothek in diesem Artikel standardmäßig nicht umgebrochene Ausgabe und ist Umbruch heute ein opt-in Feature für E-Mail und Rüstung. Das Alphabet selbst, die Padding-Regeln und die Kanonizitätsregel "Pad-Bits müssen null sein" stammen aus diesen früheren PEM- und MIME-RFCs, und 4648 wiederholt sie als die kanonischen Regeln der Familie. 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 diesen RFC aufgenommen werden konnte" - eine weitere Erinnerung daran, dass in diesem Format C kein Bürger zweiter Klasse ist. Die C-Standardbibliothek ihrerseits hat nie aufgeholt: C89 erstarrte 1990, bevor irgendetwas davon existierte, und C23 im Jahr 2024 liefert immer noch ohne Base64-Funktion. Also sind die Bibliotheken, die Sie linken, der Standard, und die Wahl zwischen ihnen ist eine kleine, aber echte Designentscheidung - worum es in diesem Artikel ging.
Kuriose kleine Fakten
Einige Fakten, die schlicht Spaß machen, alle über die Verpackungs-Seite in C:
- Die "64" ist die Basis: Jedes Ausgabe-Zeichen sind sechs Bits, und 2 hoch 6 ist 64. Das Format benennt sein Alphabet so, wie C seine Ganzzahlen benennt - danach, was die Zahl tatsächlich ist.
- Der 48-Byte-Block von OpenSSLs Streaming-Encoder ist keine willkürliche Buchführung: 48 Eingabe-Bytes sind genau 16 Gruppen zu 3, und 64 Ausgabe-Zeichen sind genau 16 Gruppen zu 4. Beide Zahlen sind Vielfache von 16, was die Art von Rundheit ist, die Hardware und Cache-Zeilen glücklich macht - oder zumindest die Menschen, die den Code lesen.
- Ein Eingabe-Byte encodiert zu vier Zeichen, zwei davon sind
=. Das kleinste mögliche nicht-leere Payload ist 50 Prozent Padding - die verschwenderischste Kodierung im Format und die, die jedes Test-Suite verwendet, weil sie so leicht falsch geschrieben wird. - Mbed TLS ist der einzige Encoder in diesem Artikel, der seine Lookups in konstanter Zeit macht, denn die Leute, die eingebettete Krypto schreiben, trauen variablem Tabellen-Indexing nicht, selbst in einem Codec, der kein Chiffre ist. Die Paranoia überträgt sich.
- Die Regel der kanonischen Kodierung - ungenutzte Pad-Bits müssen null sein - klingt trivial, bis Sie erfahren, dass es sie zu verletzen bedeutet, dass zwei verschiedene Strings zu denselben Bytes dekodieren können, was jeden "ist dieser String die Kodierung jener Datei?"-Check, der existiert, bricht. Ihre Encoder halten sich alle daran; deshalb ist base64 eine hash-stabile Darstellung und kann in einem Content-Store für einen Dateinamen stehen.
- OpenSSLs
EVP_EncodeBlockist eine der seltenen C-Funktionen, deren Rückgabewert, ihre eigene Ausgabe und ihr NUL-Terminator alle übereinstimmen: Sie schreibtnZeichen, einen NUL, und gibtnzurück. In einer Sprache, die für Off-by-one berühmt ist, 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, wo die Buchstaben in einer anderen Reihenfolge sitzen als in ASCII. Auf diesen Maschinen schließt "Encodieren" eines Strings still und leise zuerst ein Neusortieren seines Alphabets ein.
- Die leere Eingabe encodiert in jeder Bibliothek zum leeren String, ohne Pads und ohne Zeilenumbrüche. Das Neutralement des Formats, angetreten und korrekt in allen vier, was es zum billigsten Unit-Test macht, den Sie je schreiben werden.
Der Blick auf die Decoder-Seite
So war das die Verpackungs-Seite: die Mathematik, die vier Encoder, die Dialekte und die Orte, an die die Bytes gehen. Es ist die ruhige Hälfte des Jobs, denn Encodieren hat keine ungültige Eingabe und keinen Decoder, der sich mit Ihnen streitet. Die andere Richtung - auf das Base64 der Außenwelt zu treffen und die Bytes zurückzubekommen - ist der Ort, an dem sich der Schmerz konzentriert: nullgefüllte Tails, stille Abschneidung, strenge gegenüber nachsichtige Alphabete und eine Kommandozeile, die abschließende Zeilenumbrüche frisst. Base64-Dekodierung in C wird im verwandten Artikel, der von dieser Seite aus verlinkt ist, im Detail behandelt, und er ist der natürliche Begleiter zu diesem hier: Der Encoder schreibt die Box, der Decoder öffnet sie, und zusammen haben Sie 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