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 JavaScript/Node.js: Ein vollständiger Leitfaden

Ihre Anwendung erhält einen Base64-String. Er könnte der Authorization-Header einer eingehenden Anfrage sein, ein Feld in einem JSON-Payload, ein Bild, das sich in einer Data-URL versteckt, oder ein Zertifikat, das in eine Konfigurationsdatei kopiert wurde. All das ist dasselbe: Roh-Bytes in einem ASCII-Kostüm. Dieser Artikel handelt davon, dieses Kostüm in JavaScript und Node.js auszuziehen, und davon, es ohne auch nur ein einziges verlorenes Byte zu schaffen.

Ein kurzer Satz zum Format selbst: Base64 ist eine Textkodierung, die jeweils drei Eingabe-Bytes auf vier druckbare Zeichen abbildet. Die Startseite dieser Site erklärt das Alphabet, die Mathematik und das Padding in voller Detailtiefe, also bleibt es hier bei einem einzigen Satz. Eine Folge gehört in die Hosentasche: Kodierte Daten sind etwa 33 Prozent größer als die Bytes, die sie tragen, Dekodieren ist also eine schrumpfende Operation, und nichts in diesem Artikel fügt Geheimhaltung hinzu oder nimmt sie davon ab. Sie entpacken, Sie lösen kein Siegel.

Die gute Nachricht: Sie installieren nichts. Browser liefern atob() seit zwei Jahrzehnten, Node.js hat die Buffer-Klasse mit einem eingebauten base64-Modus, und moderne Runtimes liefern inzwischen Uint8Array.fromBase64(), den strengen und konfigurierbaren Neuankömmling aus der ES2026-Spezifikation. Die Kunst liegt darin, das richtige Werkzeug für den Job zu wählen, und darin, genau zu wissen, was jedes Werkzeug verzeiht, denn auf einem Server dekodieren Sie Daten von Fremden, und Verzeihung ist der Ort, an dem alles schiefgeht.

Einen Dekodierer wählen

Drei APIs decken den Großteil aller Dekodier-Arbeit ab. Sie unterscheiden sich in ihrer Persönlichkeit, und dieser Unterschied ist die ganze Geschichte:

Dekodierer Verfügbar in Persönlichkeit
Buffer.from(string, 'base64') Node.js (jede Version, die zählt) Nachsichtig: überspringt unbekannte Zeichen, stoppt beim ersten =, wirft niemals etwas
atob(string) Alle Browser, Node.js 16 und neuer Streng: wirft bei fehlerhafter Eingabe InvalidCharacterError, überspringt ASCII-Leerzeichen, verzeiht fehlendes Padding
Uint8Array.fromBase64(string) Chrome 140+, Firefox 133+, Safari 18.2+, Node.js 25+ Konfigurierbar: Sie wählen das Alphabet und wie streng der letzte Chunk sein muss

Alle drei öffnen dasselbe klassische Payload auf dieselbe Weise:

// Das Node.js-Arbeitstier
const { Buffer } = require('node:buffer');
console.log(Buffer.from('aGVsbG8gd29ybGQ=', 'base64').toString('utf8')); // "hello world"
// Das Legacy-Paar (jeder Browser, Node.js 16+)
console.log(atob('aGVsbG8gd29ybGQ=')); // "hello world", als Binärstring
// Die moderne ES2026-Methode (Chrome 140+, Node.js 25+)
console.log(new TextDecoder().decode(Uint8Array.fromBase64('aGVsbG8gd29ybGQ='))); // "hello world"

Eine Warnung, bevor Sie sich auf atob() verlassen: Es liefert einen String, aber einen Binärstring, einen String, in dem jedes Zeichen ein Roh-Byte als Codepunkt von 0 bis 255 trägt. Einen auszugeben ist in Ordnung. Speichern Sie ihn dagegen in JSON, einer Datenbank oder einem Cookie, reisen diese Roh-Byte-Werte mit, also wandeln Sie ihn sofort nach dem Dekodieren in echte Bytes oder echten Text um.

Der nachsichtige Dekodierer und was er schluckt

Der Buffer von Node ist ein nachsichtiger Leser, und das ist ein zweischneidiges Schwert. Er ist wunderbar für Daten, die holprige Wege zurückgelegt haben: MIME-E-Mails mit ihren Zeilenumbrüchen, per Hand kopierte Strings, Log-Ausgaben mit verirrten Leerzeichen. Er ist gefährlich für Daten, die Sie nicht selbst erzeugt haben, denn er beschwert sich nie. Das hier passiert tatsächlich:

Eingabe Was Buffer.from(input, 'base64') tut
'!!!' Liefert einen leeren Buffer. Der ganze Müll wird übersprungen, nichts dekodiert, kein Fehler.
'aGVsbG8== garbage' Liefert "hello". Das erste = beendet das Dekodieren; der Rest wird ignoriert.
'aG!VsbG8' Liefert "hello". Das Ausrufezeichen wird übersprungen, kein Fehler.
'aGVs=bG8' Liefert "hel". Ein = mitten im String stoppt die Vorstellung früh.
'aGVsbG8====' Liefert "hello". Extra-Padding am Ende wird ignoriert.
'=aGVsbG8' Liefert einen leeren Buffer. Padding vor den Daten bedeutet nichts.

Das Mittel gegen nicht vertrauenswürdige Eingabe ist ein Validator, und die Grammatik von Base64 ist klein genug, um in einen regulären Ausdruck zu passen:

const STRICT = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/;
function decodeStrict (base64) {
  if (!STRICT.test(base64)) {
    throw new TypeError('Not a valid base64 string');
  }
  return Buffer.from(base64, 'base64');
}
console.log(decodeStrict('aGVsbG8gd29ybGQ=').toString('utf8')); // "hello world"
try {
  decodeStrict('aGVs!bG8');
} catch (error) {
  console.log(error.message); // "Not a valid base64 string"
}

Der reguläre Ausdruck prüft die Form: Vierer-Gruppen mit korrektem Padding. Eine Regel kann er nicht prüfen, die kanonische Kodierungsregel aus RFC 4648, die besagt, dass die ungenutzten Padding-Bits der letzten Gruppe null sein müssen. Der strenge Modus von Uint8Array.fromBase64() prüft sie, also können Sie auf Node.js 25 oder einem modernen Browser den regulären Ausdruck komplett überspringen und die Plattform die Prüfung erledigen lassen:

console.log(new TextDecoder().decode(Uint8Array.fromBase64('aGVsbG8'))); // "hello", der loose-Modus verzeiht fehlendes Padding
try {
  Uint8Array.fromBase64('QQB=', { lastChunkHandling: 'strict' });
} catch (error) {
  console.log(error.name); // "SyntaxError", die Padding-Bits sind nicht null
}

Die Option lastChunkHandling hat drei Einstellungen, die sich zu kennen lohnen. "loose" (der Standard) überspringt Leerzeichen, akzeptiert fehlendes Padding und ignoriert übrig gebliebene Padding-Bits. "strict" verlangt eine vollständige, gepadderte letzte Gruppe, in der alle Padding-Bits null gesetzt sind. Und "stop-before-partial" dekodiert nur vollständige Vier-Zeichen-Gruppen und überlässt das abschließende Fragment Ihnen, um es weiterzutragen, genau das Teil, das das Dekodieren von Streams angenehm macht, wie Sie später in diesem Artikel sehen werden.

Von Bytes zu Text: Die Zeichensatz-Entscheidung

Base64 zu dekodieren liefert Ihnen Bytes. Bytes werden erst dann zu Text, wenn Sie einen Zeichensatz wählen, und diese Wahl steht Ihnen frei, in der Regel basierend darauf, was der Sender versprochen hat. Der Standard von Node ist der, den Sie die meiste Zeit wollen:

const { Buffer } = require('node:buffer');
const bytes = Buffer.from('w6k=', 'base64'); // die zwei Bytes C3 A9
console.log(bytes.toString('utf8'));   // "é", die zwei Bytes vereinigen sich zu einem Zeichen
console.log(bytes.toString('latin1')); // "é", dieselben Bytes, ein Zeichen nach dem anderen gelesen

Mit UTF-8 gibt es einen Haken: Wenn eine Byte-Folge kein gültiges UTF-8 ist, wirft Node nichts. Es setzt das Unicode-Ersatzzeichen ein (U+FFFD, den Diamanten mit Fragezeichen) und macht weiter, was bedeutet, dass ein beschädigtes Payload Ihre Pipeline hindurch bis in Ihre Datenbank segeln kann. Der eigentliche Text-Dekodierer der Plattform, TextDecoder (ein Global in Node.js und jedem Browser), hat eine fatal-Option, die Beschädigung in eine TypeError verwandelt, die Sie fangen können:

const stray = new Uint8Array([0xe9]); // ein einzelnes Byte, kein gültiges UTF-8
console.log(new TextDecoder().decode(stray)); // das Ersatzzeichen, kein Fehler
try {
  new TextDecoder('utf-8', { fatal: true }).decode(stray);
} catch (error) {
  console.log(error.name); // "TypeError"
}

Legacy-Systeme sterben nie, und TextDecoder weiß immer noch, wie man sie liest. Es akzeptiert die komplette Label-Tabelle des WHATWG Encoding Standards, also lässt sich ein Base64-Payload aus einer Windows-Anwendung der 1990er, einem japanischen Mainframe oder einem alten FTP-Mirror noch mit Labels wie 'windows-1250', 'shift_jis', 'euc-kr' oder 'gb18030' dekodieren, alle ohne Unterschied zwischen Groß- und Kleinschreibung. Ein Label verdient eine Warnung, denn es hat echte Debugging-Zeit gekostet: Die Spezifikation leitet 'iso-8859-1', 'latin1' und sogar 'us-ascii' auf den Windows-1252-Dekodierer ab. Byte 0x80, ein Steuerzeichen im echten Latin-1, kommt als Euro-Zeichen heraus:

console.log(new TextDecoder('iso-8859-1').decode(new Uint8Array([0x80]))); // "€", nicht das Latin-1, das Sie verlangt haben
// Für ein echtes, bytegenaues Latin-1-Lesen die Buffer-Seite verwenden:
console.log(Buffer.from('gA==', 'base64').toString('latin1')); // das rohe 0x80-Steuerzeichen

Wenn Sie diese rohe Abbildung wirklich brauchen, bildet die 'latin1'-Kodierung des Buffers (deren Legacy-Alias 'binary' ist, in den Worten der Node-Dokumentation, ein sehr irreführender Name) Byte N auf Codepunkt N ab, ohne den Umweg über Windows. Für alles Moderne ist UTF-8 plus fatal: true das sichere Paar.

Einen JWT aufbrechen

Das bei weitem häufigste Base64-Payload, das ein JavaScript-Service dekodiert, ist ein JSON Web Token, der xxxxx.yyyyy.zzzzz-String, der in dem Authorization-Header von halb dem Web reist. Gemäß RFC 7515 ist ein kompaktes JWS drei durch Punkte getrennte Teile, und die ersten zwei sind JSON-Objekte, kodiert mit base64url ohne Padding. Sie in Node.js zu lesen braucht keine Zeremonie, denn der base64url-Modus ist eine erstklassige Kodierung:

const { Buffer } = require('node:buffer');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiQWRhIn0.JMjpmDdNzQZpTuUO1H33GJsj7nWhBu-qxkPD0GL2uaA';
const [head, body, signature] = token.split('.');
console.log(JSON.parse(Buffer.from(head, 'base64url').toString('utf8'))); // { alg: 'HS256', typ: 'JWT' }
console.log(JSON.parse(Buffer.from(body, 'base64url').toString('utf8'))); // { sub: '123', name: 'Ada' }

Oft genug gesagt, aber es lohnt sich, es zu wiederholen: Dekodieren ist keine Verifikation. Der Header und der Payload sind nur verkleidet, nicht verschlüsselt, und jeder, der den Token hält, kann beide lesen. Der Teil, den Sie prüfen müssen, ist der dritte, die Signatur. Für ein klassisches HMAC-SHA256-Token ist der gesamte Check ein paar Zeilen des eingebauten crypto-Moduls, und der einzige subtile Teil ist der Vergleich mit timingSafeEqual, damit ein Angreifer die Dauer Ihres Byte-für-Byte-Vergleichs nicht ausnutzen kann:

const crypto = require('node:crypto');
const expected = crypto.createHmac('sha256', 'topsecret').update(head + '.' + body).digest();
const actual = Buffer.from(signature, 'base64url');
console.log(crypto.timingSafeEqual(expected, actual)); // true
console.log(crypto.timingSafeEqual(crypto.createHmac('sha256', 'wrong-secret').update(head + '.' + body).digest(), actual)); // false

In einem echten Service basteln Sie sich das in der Regel nicht selbst. Das Paket jose (null Abhängigkeiten, läuft in Node.js, Browsern und Edge-Runtimes) und das langjährige Paket jsonwebtoken (Node.js) verpacken den Tanz, behandeln die RSA- und ECDSA-Algorithmus-Familien und erzwingen die Claims exp, aud und iss. Welches Paket Sie auch wählen, die Base64-Verrohrung darunter sind dieselben zwei Aufrufe, die Sie gerade gesehen haben.

HTTP: Header, Query-Strings und Cookies

Drei Ecken des Kabels sind voller Base64. Die älteste ist die HTTP-Basic-Authentifizierung, definiert in RFC 7617: Der Client sendet Authorization: Basic plus die Base64-Kodierung von user-id:password. Auf dem Server ist das ein Scheibchen und ein Dekodieren, mit dem kleinen Protokoll-Detail, dass nur der erste Doppelpunkt den Benutzernamen vom Passwort trennt, so dass ein Passwort legal weitere Doppelpunkte enthalten darf, ein Benutzername aber nicht:

const { Buffer } = require('node:buffer');
const header = 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==';
const credentials = Buffer.from(header.slice(6), 'base64').toString('utf8');
const [user, ...rest] = credentials.split(':');
console.log(user, rest.join(':')); // "Aladdin" "open sesame"

Und denken Sie daran, was Basic-Authentifizierung tatsächlich ist: Verschleierung, keine Sicherheit. Die Anmeldedaten überqueren das Kabel in einem Kostüm, deshalb ist das Verfahren nur über HTTPS akzeptabel. Die zweite Ecke ist der Query-String, und er versteckt die gemeinste Landmine im Base64-Land:

const params = new URLSearchParams('token=aGVs+bG8=');
console.log(params.get('token')); // "aGVs bG8=", das Plus ist zu einem Leerzeichen geworden

Ihr Base64 hat sich nicht selbst beschädigt. Die URL-Ebene hat es im Auftrag der Formkodierungs-Regeln höflich erledigt, denn die behandeln + als Leerzeichen. Genau deshalb verwenden Tokens, die in Query-Strings leben, das URL-sichere Alphabet, behandelt im Abschnitt unten. Die dritte Ecke ist der Cookie: Cookies sind nur ASCII, also ist jeder nicht-ASCII-Wert, der in einem gespeichert ist, mit hoher Wahrscheinlichkeit Base64, und das alte Muster, einen JSON-Blob in Base64 zu kodieren und in einem Cookie zu speichern, lebt in erstaunlich vielen Produktionssystemen. Das Dekodieren ist dasselbe, das Sie schon kennen; validieren Sie nur zuerst die Form, denn ein Cookie ist genau die Art von Ort, an dem Ihnen ein Nutzer, oder eine Browser-Erweiterung, Müll in die Hand drücken kann.

Dateien, Bilder und Data-URLs

Das Dateisystem von Node spricht Base64 direkt, also kann eine ganze Datei in einer Zeile eine JSON-Grenze überqueren:

const fs = require('node:fs');
const base64 = fs.readFileSync('./photo.png', 'base64');
console.log(base64.length); // die Datei, etwa 33 Prozent schwerer
const bytes = Buffer.from(base64, 'base64');
fs.writeFileSync('./photo.copy.png', bytes);

Das andere dateiformige Payload ist die Data-URL, der data:image/png;base64,...-String, den Front Ends für Inline-Bilder lieben. Das Rezept in jeder Runtime ist dasselbe: Beim ersten Komma aufschneiden, die Metadaten davor parsen, den Rest dekodieren. Hier wird ein echtes Ein-Pixel-PNG wieder zum Leben erweckt:

const dataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
const comma = dataUrl.indexOf(',');
const meta = dataUrl.slice(5, comma);
const bytes = Buffer.from(dataUrl.slice(comma + 1), 'base64');
console.log(meta); // "image/png;base64"
console.log(bytes.subarray(0, 8).toString('hex')); // "89504e470d0a1a0a", die PNG-Signatur

Die Signatur zu prüfen ist eine günstige Gewohnheit. Die ersten acht Bytes eines PNG sind immer 89 50 4E 47 0D 0A 1A 0A, und ein JPEG beginnt mit FF D8 FF. Wenn ein "Base64-Bild" von einem Client nicht mit den versprochenen Magie-Bytes beginnt, wissen Sie es jetzt, bevor Sie damit irgendetwas Aufwändiges machen.

URL-sicheres Base64: Das Alphabet für Tokens

Klassisches Base64 verwendet + und / als seine beiden Sonderzeichen (RFC 4648, Abschnitt 4), und beide sind in URLs ein Problem: + wird beim Form-Dekodieren zu einem Leerzeichen, und / ist ein Pfadtrenner. Die URL- und Dateinamen-sichere Variante aus Abschnitt 5, die alle base64url nennen, tauscht sie gegen - und _ aus, und darf das abschließende =-Padding komplett weglassen, wenn die Länge aus dem Kontext bekannt ist. Das ist genau die Kombination, die JWTs, OAuth-Tokens und Deep-Links brauchen, also ist base64url das Alphabet, das Sie in der Wildnis am häufigsten antreffen werden.

Der Buffer von Node macht die ganze Sache zu einem Nicht-Ereignis. Beide Dekodier-Modi, 'base64' und 'base64url', akzeptieren alle vier Sonderzeichen und bilden sie auf dieselben Werte ab, also dekodieren ein JWT-Teil, ein OAuth-Token und ein klassischer Base64-Blob alle ohne jede Zeichen-Tausch-Zeremonie:

const { Buffer } = require('node:buffer');
console.log(Buffer.from('aGVs-bG8', 'base64').toString('hex'));    // "68656cf9b1bc"
console.log(Buffer.from('aGVs+bG8', 'base64url').toString('hex')); // "68656cf9b1bc", dieselben sechs Bytes

Die ES2026-API ist absichtlich wählerischer, und sie gibt Ihnen dieselbe Flexibilität mit einer expliziten Stellschraube. Die Option alphabet wählt zwischen "base64" (der Standard, + und /) und "base64url" (- und _), und ein Zeichen aus dem falschen Alphabet ist eine SyntaxError, keine stille Dekodierung quer über die Alphabete hinweg:

console.log(Uint8Array.fromBase64('aGVs-bG8', { alphabet: 'base64url' }).length); // 6
try {
  Uint8Array.fromBase64('aGVs-bG8'); // das Standard-Alphabet ist das klassische
} catch (error) {
  console.log(error.name); // "SyntaxError", der Strich ist kein klassisches Zeichen
}

In einem Browser, der die neuen Methoden noch nicht hat, ist der Umweg ein kleiner Tausch, bevor Sie den String atob() übergeben, das nur das klassische Alphabet kennt. Sie müssen auch das Padding wiederherstellen, falls der Sender es weggelassen hat, was für Token-artige Payloads der Normalfall ist:

function decodeBase64Url (value) {
  const classic = value.replace(/-/g, '+').replace(/_/g, '/');
  const padded = classic + '='.repeat((4 - (classic.length % 4)) % 4);
  const binary = atob(padded);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
}
console.log(new TextDecoder().decode(decodeBase64Url('aGVsbG8gd29ybGQ'))); // "hello world"

Base64 in der Wildnis: Wo sich Payloads verstecken

Base64 ist der Postdienst für Bytes in der JavaScript-Welt. Eine Tour durch die Orte, an denen es auftaucht, mit dem Dekodier-Rezept für jede Station:

  • Felder von JSON-APIs, bei weitem der häufigste Träger: Avatare, Miniaturansichten, erzeugte Dokumente und Uploads kommen als Base64-Strings im gewöhnlichen JSON an, denn JSON hat kein Wort für "das hier sind Bytes". Dekodieren Sie das Feld, bevor Sie irgendetwas anderes damit machen.
  • Umgebungsvariablen und Konfigurationsdateien: mehrere Secret-Manager, CI-Systeme und die npm-CLI selbst reichen Ihnen Base64-Blobs (ältere npm-Versionen speicherten die Registry-Anmeldedaten in .npmrc als Base64 von user:password; moderne npm schreibt einen rohen Bearer-Token in _authToken). Dekodieren Sie einmal beim Start, und halten Sie den Klartext nur so lange im Speicher, wie Sie ihn brauchen.
  • Kubernetes und Cluster-Tooling: k8s-Secrets sind berüchtigt dafür, im API und in etcd Base64-kodiert zu sein, und die offizielle Doku wiederholt unbeirrbar, dass es Kodierung ist, keine Verschlüsselung. Ihr Dekodier-Code sollte das Ergebnis als Geheimnis behandeln, nicht als Beweis für Sicherheit.
  • Datenbanken: alles Binäre, das in einer JSON-Spalte gespeichert wird (Postgres jsonb, MongoDB-Dokumente, Redis), ist häufig ein Base64-String. Dekodieren Sie es im Lese-Pfad in einen Buffer oder eine Uint8Array, und halten Sie die Datenbank rein textbasiert.
  • E-Mail: MIME-Base64 mit seinem 76-Zeichen-Zeilenumbau ist der Weg, wie Anhänge und binäre Header SMTP überqueren, ein Protokoll, das ursprünglich nur 7-Bit war. Der Dekodierer von Node überspringt die Zeilenumbrüche für Sie, also dekodiert der gesamte Körper in einem Aufruf ohne Aufräumarbeit.
  • CI- und CD-Pipelines: Build-Systeme und Secret-Injektoren übergeben Tokens als Base64-Umgebungs-Werte; dekodieren Sie im Pipeline-Skript, und geben Sie den dekodierten Wert niemals in ein Log aus.
  • Verzeichnis- und SAML-Daten: LDIF-Dateien speichern binäre Attribute (denken Sie: Zertifikate) als Base64, und SAML-Antworten werden oft deflated und dann Base64-kodiert, bevor sie eine HTTP-Grenze überqueren.
  • Worker-Threads und Edge-Runtimes: Base64-Strings überqueren die worker_threads-Grenze als gewöhnliche, structured-clone-fähige Strings, also kann ein schweres Dekodieren auf einem Worker leben, während die Event-Loop des Haupt-Threads frei bleibt.

Zwei dieser Stationen verdienen einen genaueren Blick, denn sie tauchen sowohl in Interviews als auch in der Produktion auf:

const { Buffer } = require('node:buffer');
// Umgebungsvariable: das Geheimnis kommt Base64-kodiert an
const token = Buffer.from(process.env.REGISTRY_TOKEN_B64, 'base64').toString('utf8');
// JSON-API-Feld: entpacken, bevor Sie irgendetwas anderes machen
const body = { attachment: 'iVBORw0KGgo...' };
const imageBytes = Buffer.from(body.attachment, 'base64');
console.log(imageBytes.subarray(0, 4).toString('hex')); // "89504e47", wieder die PNG-Signatur
// MIME-E-Mail: die Zeilenumbrüche werden übersprungen, keine Aufräumarbeit nötig
const mimeBody = 'SGVsbG8sIHdyYXBw\nZWQgYmFzZTY0IQ==';
console.log(Buffer.from(mimeBody, 'base64').toString('utf8')); // "Hello, wrapped base64!"

Das Anti-Pattern, das Sie auf dieser Tour erkennen sollten, ist überall dasselbe: Base64 an einer Stelle, an der Roh-Bytes schon erlaubt waren. Ein WebSocket-Frame, ein Datei-Stream, eine Postgres-bytea-Spalte, alle tragen Bytes nativ, also ist ein Base64-Rundweg dort reiner Overhead, die 33-Prozent-Größensteuer ohne jeden Nutzen. Wenn ein nativer Binär-Pfad existiert, nehmen Sie ihn.

Dekodieren in Stücken: Streams und große Daten

Vierer-Gruppen von Base64-Zeichen kodieren drei Bytes, also kann ein Stream von Chunks eine Gruppe halbieren. Der naive Ansatz, bei dem Sie jeden Chunk dekodieren und beten, beschädigt die Ausgabe an zufälligen Grenzen. Die ES2026-API wurde genau dafür entworfen: setFromBase64() schreibt in ein vorreserviertes Array und meldet, wie viele Eingabe-Zeichen es verbraucht hat, und der "stop-before-partial"-Modus lässt es bei der letzten vollständigen Gruppe stoppen und das Fragment für den nächsten Chunk liegen. Das Muster spiegelt die Stream-API des TextDecoder:

const { Buffer } = require('node:buffer');
const chunks = ['aGVsbG8', 'gd29ybGQ='];
let leftover = '';
const parts = [];
for (const chunk of chunks) {
  const pending = leftover + chunk;
  const space = new Uint8Array(Math.ceil(pending.length * 3 / 4));
  const { read, written } = space.setFromBase64(pending, { lastChunkHandling: 'stop-before-partial' });
  parts.push(Buffer.from(space.buffer, space.byteOffset, written));
  leftover = pending.slice(read);
}
parts.push(Buffer.from(Uint8Array.fromBase64(leftover)));
console.log(Buffer.concat(parts).toString('utf8')); // "hello world"

Auf Runtimes ohne die neuen Methoden (und in der LTS-Linie von Node gab es sie eine Weile nicht) funktioniert derselbe Loop mit einem kleinen Userland-Dekodierer, der eine unvollständige Gruppe verfolgt, oder Sie puffern die eingehenden Chunks einfach, bis Sie an Gruppen-Grenzen schneiden können. Die wichtige Idee ist das Weitertragen: Dekodieren Sie nie ein Fragment allein.

Große Payloads bringen zwei weitere Grenzen zu Ihrer Aufmerksamkeit. Erstens der String selbst: buffer.constants.MAX_STRING_LENGTH von Node ist 536870888 Zeichen, grob 512 MiB an Text, was zu etwa 400 MB an Bytes dekodiert. Eine "Base64-Datei" größer als das braucht einen Streaming-Ansatz, nicht einen einzelnen readFileSync. Zweitens der Speicher: Der kodierte String lebt als UTF-16 im JavaScript-Heap, zwei Bytes pro Zeichen, und der dekodierte Buffer ist eine zweite Kopie der Daten. Bei großen Payloads halten Sie beides nur kurz in der Hand, also lassen Sie die kodierte Form nur so lange lebendig, wie der Code es zulässt, und ziehen Sie für alles Dateigrößige Streams vor.

Vom Terminal aus

Node verdoppelt sich als völlig brauchbarer Base64-Dekodierer für die Kommandozeile, was praktisch ist, wenn Sie eine Anfrage debuggen oder einen Konfigurations-Wert inspizieren:

# Ein klassischer Base64-String, der als Argument übergeben wird
node -e 'console.log(Buffer.from(process.argv[1], "base64").toString("utf8"))' "aGVsbG8gd29ybGQ="
# Die URL-sichere Variante, Padding optional
node -e 'console.log(Buffer.from(process.argv[1], "base64url").toString("utf8"))' "aGVsbG8gd29ybGQ"
# Vom stdin dekodieren, wofür Pipes da sind
echo -n "aGVsbG8gd29ybGQ=" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>console.log(Buffer.from(d.trim(),"base64").toString("utf8")))'

Alle drei geben hello world aus. Wenn Sie auf dem Rechner auch den klassischen base64-Befehl aus coreutils haben, erledigt er mit base64 -d denselben Job, aber die Node-Versionen kennen base64url, das das traditionelle Werkzeug nicht kennt.

Fallen mit JavaScript-Akzent

Jede dieser Fallen war schon mal ein verlorener Nachmittag in JavaScript oder Node.js:

  • Der stille Dekodierer: Buffer.from('!!!', 'base64') liefert einen leeren Buffer, keinen Fehler. Halbbeschädigte Eingabe dekodiert zu halbbeschädigten Daten ohne Warnung. Validieren Sie nicht vertrauenswürdige Eingabe mit dem strengen regulären Ausdruck (oder dem strengen fromBase64-Modus), und behandeln Sie einen leeren Buffer aus einem nicht-leeren String als rote Flagge.
  • Das fehlende Kodierungs-Argument: Buffer.from('aGVsbG8=') ohne zweites Argument dekodiert nichts. Er baut einen Buffer aus den UTF-8-Bytes dieser Buchstaben, also sind Ihre "dekodierten" Daten die Buchstaben selbst, als Bytes neu verpackt. Das 'base64'-Argument ist der ganze Trick.
  • Das Binärstring-Kostüm: die Ausgabe von atob() ist kein Text, bis Sie es sagen. Es in eine JSON-Antwort, einen Cookie oder eine Log-Zeile zu stopfen "funktioniert", und es bewahrt dabei jedes Null-Byte, was Log-Shipper und Serialisierer gleichermaßen überrascht. Konvertieren Sie mit charCodeAt() sofort in eine Uint8Array, oder in UTF-8-Text.
  • Das Plus im Query-String: ein + in einem form-dekodierten Query-Wert ist ein Leerzeichen, zur Zeit URLSearchParams ihn an Sie übergibt. Bevorzugen Sie base64url für alles, was in einer URL lebt, und fügen Sie niemals ein klassisches Base64-Token ohne Escaping in einen Query-String ein.
  • Das Ersatzzeichen: ungültiges UTF-8 wird im UTF-8-Modus des Buffers zu einem stillen Diamanten-mit-Fragezeichen, statt zu einem Fehler, also kann ein beschädigtes Payload Ihre Pipeline passieren und in einer Datenbank landen. Schalten Sie bei TextDecoder fatal: true ein, wo Beschädigung eine laute Fehlschlag sein sollte.
  • Der Umweg über Windows: Wenn Sie TextDecoder um 'iso-8859-1' oder 'latin1' bitten, bekommen Sie den Windows-1252-Dekodierer, wo Byte 0x80 zum Euro-Zeichen wird. Für echtes, bytegenaues Latin-1 lesen Sie den Buffer stattdessen mit toString('latin1'). Und denken Sie daran, dass 'binary' nur ein irreführender Alias für dieselbe Latin-1-Abbildung ist.
  • Die Größen-Grenzen: buffer.constants.MAX_LENGTH sind auf 64-Bit-Systemen 9007199254740991 Bytes (2 hoch 53, minus eins), aber der String, der das Base64 trägt, kann nicht über MAX_STRING_LENGTH von 536870888 Zeichen hinauswachsen. Ein einzelner String kann also nur etwas über 400 MB an dekodierten Daten tragen; darüber hinaus: streamen.
  • Die Speicher-Rechnung: ein Base64-String kostet zwei Heap-Bytes pro Zeichen (UTF-16), und der dekodierte Buffer ist eine volle zweite Kopie. Eine 100-MB-Datei bedeutet kurz etwa 133 MB an String plus 100 MB an Buffer in Ihrem Prozess. Verkleinern Sie das Fenster, in dem die kodierte Form referenziert bleibt.
  • Die Diskrepanz: streng in der Ferne, nachsichtig in der Nähe: Ihr Node-Dekodierer verzeiht, was ein strenger Dekodierer woanders ablehnt (ein Python-Skript, ein Go-Service, eine Mobile-App). Wenn eine Seite Ihres Systems streng ist und die andere nachsichtig, taucht der Bug nur bei bestimmten Payload-Längen auf - das ist die schlechteste Art von Bug. Vereinbaren Sie die Strenge auf Protokoll-Ebene, nicht im Kopf.

Wie JavaScript seine Dekodierer wachsen ließ

Die Browser-Seite hat eine lange, langweilige, verlässliche Geschichte. atob() und btoa() wurden Anfang 2011 im HTML5-Entwurf spezifiziert (die Browser hatten sie, bevor die Spezifikation es tat), und sie sitzen seitdem in jedem großen Browser, ihr Verhalten seit über einem Jahrzehnt unverändert. Sie sind älter als die Typed Arrays im Sprachstandard (ES2015), deshalb sprechen sie von "Binärstrings" statt von Bytes.

Node.js ließ seinen Dekodierer auf einer anderen Zeitleiste wachsen. Die Buffer-Klasse wurde in Version 0.1.103, im Sommer 2010, zu einem Global, knapp fünf Jahre vor Node 1.0, und sie trug den 'base64'-Modus von Anfang an. Den größten Teil des Lebens von Node war das der einzige Dekodierer im Ort. Dann kam die Web-Standard-Welle: Node 16 im Jahr 2021 fügte atob() und btoa() als Globals hinzu, damit Code, der für den Browser geschrieben wurde, auf dem Server ohne Polyfill lief, und markierte beide ab Tag eins als Legacy. Node 25, veröffentlicht am 15. Oktober 2025, upgradete V8 auf 14.1 und brachte die ES2026-Methoden, Uint8Array.fromBase64(), setFromBase64() und ihre hex-Brüder, in die Runtime. Auf dem Weg wurde der alte new Buffer()-Konstruktor deprecatet (Node 10 startete die Warnungen 2018) zugunsten von Buffer.from(), alloc() und allocUnsafe(), zum Teil, weil eine uninitialisierte Zuteilung den Speicher weitergeben konnte, der davor dort gelegen hatte.

In den Browsern landete dieselbe Welle etwas früher: Firefox 133 und Safari 18.2 lieferten die neuen Methoden 2024, und Chrome 140 (stabil am 2. September 2025) vollendete das Set, woraufhin das Feature im Baseline-Programm der Browser-Hersteller als Baseline Newly available erklärt wurde. Bun, die All-in-one-JavaScript-Runtime, bekam sie in Version 1.1.22 im August 2024. Und wenn Sie keine aktuelle Runtime verlangen können, liefern core-js und das Paket es-arraybuffer-base64 aus dem es-shims-Projekt Polyfills für all das, was auch der Weg ist, den die meisten Frameworks intern gehen.

Das Format, dem sie dienen, hat eine noch ältere Genealogie. Das Alphabet wurde 1987 zuerst für Privacy-Enhanced Mail standardisiert (RFC 989), die Revision von 1993 (RFC 1421) behielt dasselbe Alphabet, und MIME nahm es 1996 auf (RFC 2045), etwa drei Jahre nach jener Revision, mit seinem 76-Zeichen-Zeilenumbau; RFC 3548 im Jahr 2003 vereinigte base16, base32 und base64 in einem Dokument, und RFC 4648 im Jahr 2006 gab es neu heraus, und behielt das URL-sichere Alphabet, das RFC 3548 hinzugefügt hatte - das eine, das ein Jahrzehnt später in jedem JWT landen würde. Die URL-sichere Variante ist eine nette Kuriosität: Sie wurde 2001 in einem Mailing-List-Post über Peer-to-Peer-Identifikatoren vorgeschlagen, bevor sie jemals ein Token traf.

Fun-Fakten für Ihr nächstes Standup

  • Der Beispiel-Key aus dem WebSocket-RFC, dGhlIHNhbXBsZSBub25jZQ==, dekodiert zu den Worten "the sample nonce". Das Normungsgremium versteckte ein Augenzwinkern in seinem eigenen Beispiel, und das atob() von Node knackt den Scherz in einem Aufruf.
  • Buffer.from('!!!', 'base64') liefert einen Buffer der Länge null. Eine echte Zuteilung, in der nichts drin ist. Nichts. Das kommt bei Node einem Schulterzucken am nächsten.
  • Die Base64-Dekodierer von Node sind auf eine Weise zweisprachig, die die Spezifikation nie verlangt hat: +, -, / und _ sind in beiden Modi, 'base64' und 'base64url', willkommen, jedes Paar bildet auf denselben Wert ab.
  • Die Node-Dokumentation für atob() enthält den Satz "Verwenden Sie stattdessen Buffer.from(data, 'base64')". Eine Runtime, die Ihnen sagt, auf einen ihrer eigenen Globals zu verzichten, komplett mit einem offiziellen Codemod (npx codemod@latest @nodejs/buffer-atob-btoa), der die Migration für Sie erledigt.
  • Kleine Buffer werden aus einem gemeinsamen Block herausgeschnitten: Buffer.poolSize ist 65536 Bytes, und jede kleine Zuteilung wiederverwendet Stücke aus diesem Block. Deshalb ist die Buffer-Erzeugung schnell, und deshalb ist "unsafe"-Zuteilung ein Ausdruck, dessen Bedeutung Sie kennen sollten.
  • Das kleine Paket base64-js, drei Funktionen und null Abhängigkeiten, zieht auf npm gut über 100 Millionen Downloads pro Woche, fast alles davon als versteckte Abhängigkeit innerhalb anderer Pakete. Base64 ist der am meisten geschmuggelte Code im Ökosystem.
  • Uint8Array.fromBase64() hat einen Modus namens "stop-before-partial", der existiert, allein damit Sie einen Stream dekodieren können, ohne jemals eine Vier-Zeichen-Gruppe zu teilen. Ein Modus, der nach der Sache benannt ist, die er verweigert zu tun, ist ein seltenes Stück API-Poesie.
  • Die Unix-Passwort-Welt verwendet ihre eigenen Base64-artigen Alphabete, ohne Padding, und, verwirrenderweise, stehen sie nicht alle in derselben Reihenfolge. Das klassische crypt(3)-"hash64"-Alphabet ist ./0-9A-Za-z, aber bcrypt mischt dieselben 64 Zeichen stattdessen zu ./A-Za-z0-9. Die bcrypt-Version treffen Sie in den $2b$-Hashes, die viele JavaScript-Projekte für Benutzer-Passwörter speichern, und es ist der Grund, warum "base64" in einem Sicherheits-Kontext mehrere verschiedene Alphabete bedeuten kann, nicht nur zwei.

Nur noch eine Richtung

Base64 in JavaScript und Node.js zu dekodieren ist ein Stapel aus drei ehrlichen Werkzeugen: Buffer.from(string, 'base64'), das nachsichtige Arbeitstier, das beide Alphabete akzeptiert und jedes verirrte Zeichen überspringt, am besten bewacht von einem strengen regulären Ausdruck; TextDecoder, für echten Text in jedem Zeichensatz, den das alte Web je erfunden hat, mit fatal-Modus, wenn Beschädigung wehtun sollte; und das neue Uint8Array.fromBase64(), für Byte-first-Code, der strenge Alphabete, strenge Padding-Bits und Streaming ohne Akrobatik will. Verankern Sie den Zeichensatz, validieren Sie, was Fremde Ihnen schicken, vergleichen Sie Signaturen mit timingSafeEqual, und das Format hört auf, ein Rätsel zu sein, auf beiden Seiten der Browser/Server-Grenze.

Und wenn Sie fertig damit sind, Pakete zu öffnen, denken Sie daran, dass jemand sie versiegelt haben muss. Die Kodierungs-Seite hat ihre eigenen Fallen: die Unicode-Wand, die btoa() mitten im Satz stoppt, der MIME-Zeilenumbau, die Padding-Regeln von base64url, und das neue Uint8Array.toBase64() mit seiner omitPadding-Option. Diese Geschichte, mit Code-Beispielen für jeden Schritt, wird ausführlich in dem verwandten Base64-Kodierungs-Artikel auf unserer Schwestersite behandelt. Lesen Sie ihn als Nächstes, denn die Fallen sind auf der anderen Seite des Alphabets anders und lustiger.

Zuletzt aktualisiert: 2026-09-07

Verwandter Artikel: Base64-Kodierung in JavaScript/Node.js: Ein vollständiger Leitfaden