← Alle Artikel

Base64 ist keine Verschlüsselung: Was es verbirgt und was nicht

Base64 ist Codierung, keine Verschlüsselung. Warum Passwörter, JWT-Payloads, API-Keys, Basic-Auth-Header, URLs und Logs lesbar bleiben.

„Wir können das doch einfach Base64-codieren, bevor wir es speichern." Wer genug Config-Dateien, interne Dashboards, Browser-Bundles, Webhook-Handler oder Auth-Code reviewt, hört diesen Satz früher oder später. Er klingt plausibel, weil die Ausgabe verwürfelt aussieht:

c2VjcmV0IHBhc3N3b3Jk

Aber Base64 ist keine Verschlüsselung. Es ist kein Passwortschutz. Es ist keine sichere Methode, um API-Keys, JWT-Claims, User-IDs, Basic-Auth-Zugangsdaten oder private Daten in einer URL zu verstecken. Derselbe Wert lässt sich mit einem Aufruf zurückverwandeln:

atob("c2VjcmV0IHBhc3N3b3Jk");
// "secret password"

Die Faustregel ist unmissverständlich: Wenn der Ursprungswert ohne geheimen Schlüssel zurückkommt, hat Base64 ihn nicht geschützt. Es hat lediglich die Darstellung verändert.

Der schnelle Sicherheitstest

Wenn du Base64 im Code siehst, frage dich, welche Aufgabe es erfüllt:

Base64 wird verwendet für... Guter Zweck? Worauf achten
Bytes in JSON stopfen Ja Größenlimits im Rahmen halten
Eine kleine data:-URI einbetten Ja Große Dateien nicht blind inlinen
Ciphertext, Nonce, Salt oder Signatur-Bytes als Text speichern Ja Der Krypto-Schritt muss vor Base64 passieren
Einen opaken Pagination-Cursor tragen Meistens Als Interface-Detail behandeln, nicht als Zugriffskontrolle
Einen Basic-Auth-Header bauen Nur mit HTTPS TLS schützt den Request, Base64 nicht
Einen API-Key im Frontend-Code verstecken Nein Jeder Browser bekommt Wert und Decoder
Ein Passwort speichern Nein Langsames Passwort-Hashing verwenden
JWT-Claims verstecken Nein Signierte JWT-Payloads sind lesbar, es sei denn als JWE verschlüsselt
User-IDs oder Rollen in URLs verstecken Nein Serverseitige Autorisierung muss trotzdem laufen

Wenn dir dabei „damit es niemand lesen kann" durch den Kopf geht, beschreibst du nicht Base64. Du beschreibst Verschlüsselung, Hashing, Signieren, Zugriffskontrolle oder Secret-Management.

Was Base64 tatsächlich tut

Base64 ist ein Binär-zu-Text-Codierungsschema. RFC 4648 definiert das gängige Base64-Alphabet:

A-Z a-z 0-9 + / =

Das = ist Padding. Base64 gruppiert die Eingabebytes in 24-Bit-Blöcke: aus 3 Eingabebytes werden 4 druckbare Zeichen. Deshalb ist die Base64-Ausgabe etwa 33 % größer als die Rohbytes vor jeder Kompression.

btoa("hello");
// "aGVsbG8="

atob("aGVsbG8=");
// "hello"

Derselbe Round-Trip funktioniert im Terminal:

printf '%s' 'secret password' | base64
# c2VjcmV0IHBhc3N3b3Jk

printf '%s' 'c2VjcmV0IHBhc3N3b3Jk' | base64 -d
# secret password

Kein Passwort-Prompt, kein privater Schlüssel, kein Entschlüsselungsschritt, kein Salt, keine Nonce, kein IV, kein Tag, kein Work Factor. Wer den codierten String hat, kann ihn zurückverwandeln.

Base64 vs. Base64url

Standard-Base64 ist nicht immer URL-sicher, weil +, / und = in URLs, Dateinamen, Cookies und Token-Segmenten unhandlich sein können. Base64url ändert das Alphabet:

Merkmal Standard-Base64 Base64url
Zeichen 62 und 63 + und / - und _
Padding Meist = Oft weggelassen
Typische Orte MIME, PEM, Basic Auth, Binärfelder JWTs, URL-Token, Dateinamen
Sicherheitsunterschied Keiner Keiner

Beim JWT-Debugging ist das relevant. Ein JWT nutzt Base64url, nicht das gepolsterte Standard-Base64:

header.payload.signature

Wenn du ein JWT-Segment in einen Standard-Base64-Decoder klebst und es scheitert, ist der String nicht zwangsläufig geheim oder verschlüsselt. Er muss vielleicht nur als Base64url behandelt werden:

function base64urlToBase64(part) {
  let out = part.replace(/-/g, "+").replace(/_/g, "/");
  while (out.length % 4 !== 0) out += "=";
  return out;
}

Diese Umwandlung tauscht das Alphabet. Sie fügt keine Vertraulichkeit hinzu.

UTF-8: Die Browser-Falle

btoa() und atob() im Browser arbeiten auf Binärstrings. Reines ASCII ist unproblematisch:

btoa("hello");
// "aGVsbG8="

Aber Unicode-Text braucht vorher einen Byte-Schritt. Das überrascht Teams, die mit hello testen und später Kundennamen, chinesischen Text, Emoji oder Arabisch codieren:

btoa("你好");
// InvalidCharacterError in browsers

UTF-8-Bytes verwenden:

function utf8ToBase64(text) {
  const bytes = new TextEncoder().encode(text);
  const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join("");
  return btoa(binary);
}

function base64ToUtf8(base64) {
  const binary = atob(base64);
  const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}

const encoded = utf8ToBase64("你好");
base64ToUtf8(encoded);
// "你好"

Das Base64 Encode & Decode-Tool auf dieser Seite erledigt den UTF-8-Round-Trip im Browser, damit Nicht-ASCII als Text und nicht als Mojibake ankommt.

Warum Entwickler Base64 mit Verschlüsselung verwechseln

Base64 taucht oft in der Nähe echter Sicherheit auf:

  • TLS-Zertifikate werden als PEM-Text gespeichert.
  • JWTs nutzen Base64url-Abschnitte.
  • HTTP Basic Auth kodiert username:password mit Base64.
  • SSH-Public-Keys enthalten Base64-codiertes Schlüsselmaterial.
  • Verschlüsselter Ciphertext wird vor Speicherung oder Transport oft Base64-codiert.

Der letzte Punkt sorgt für die meiste Verwirrung. Verschlüsselte Daten können Base64-codiert sein, aber Base64 ist nicht die Verschlüsselung. Die Sicherheit kommt aus einer kryptographischen Operation wie AES-GCM, XChaCha20-Poly1305, RSA-OAEP, JWE oder TLS. Base64 hat nur dafür gesorgt, dass sich die resultierenden Bytes bequem in JSON, E-Mail, Header oder eine Datenbankspalte packen lassen.

Präzise Wörter im Code und im Produkttext:

encodedBackup      // OK, wenn es nur Base64 ist
encryptedBackup    // Nur OK, wenn echt verschlüsselt wurde
signedState        // OK, wenn HMAC oder Signatur die Integrität schützt
passwordHash       // OK, wenn ein Passwort-Hashing-Algorithmus es erzeugt hat

Namen sind hier nicht kosmetisch. Ein irreführender Name kann spätere Reviewer glauben lassen, es gebe eine Sicherheitsgrenze, wo keine ist.

Encoding vs. Verschlüsselung vs. Hashing vs. Signieren

Diese Operationen lösen unterschiedliche Probleme:

Operation Hauptaufgabe Geheimnis nötig? Umkehrbar? Beispiel
Encoding Darstellung ändern Nein Ja Base64, Base64url, URL-Encoding
Verschlüsselung Klartext verbergen Ja Ja, mit Schlüssel AES-GCM, XChaCha20-Poly1305, JWE
Hashing Einwegfingerabdruck Nein Nein SHA-256 für Prüfsummen
Passwort-Hashing Langsame Passwortprüfung Salt plus Kosten-Parameter Nein Argon2id, bcrypt, scrypt
Signatur / MAC Integrität und Herkunft belegen Ja, oder Schlüsselpaar Nein HMAC, JWS, Ed25519-Signatur

Base64 beantwortet die Frage „Kann diese Bytefolge durch einen Textkanal reisen?". Es beantwortet nicht „Wer darf das lesen?", „Wer hat das geändert?" oder „Darf dieser User auf diesen Datensatz zugreifen?".

Realer Sicherheitsfehler 1: API-Keys im Frontend-Code versteckt

Dieses Muster taucht in React-Apps, Browser-Extensions, WordPress-Themes und internen Admin-Tools auf:

// Der Key wird trotzdem an jeden Browser ausgeliefert.
const apiKey = atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=");

fetch("https://api.example.com/report", {
  headers: { Authorization: `Bearer ${apiKey}` },
});

Base64 hilft nicht, weil der Browser sowohl den codierten Wert als auch den Decoder erhält. Jeder kann DevTools öffnen, das JavaScript-Bundle inspizieren, denselben atob()-Aufruf ausführen und den Key kopieren.

Der Fix ist architektonisch:

  • Echte Secrets bleiben auf dem Server.
  • Browser-Keys bekommen enge Scopes und Domain-Restriktionen.
  • Sensible Upstream-Calls über das eigene Backend proxyen.
  • Jedes Secret rotieren, das schon an Nutzer ausgeliefert wurde.
  • Damit rechnen, dass Secret-Scanner Base64-artige Strings decodieren.

Einen Key zu codieren macht das Code-Review schwerer. Es macht den Key nicht sicherer.

Realer Sicherheitsfehler 2: Passwörter als Base64 gespeichert

Das ist deshalb so schädlich, weil es jahrelang leise in einer Datenbank sitzen kann:

// Umkehrbare Speicherung. Bitte nicht.
const storedPassword = btoa(password);

// Später:
const password = atob(storedPassword);

Wenn die Datenbank leakt, kommt jedes Passwort sofort zurück. Es gibt keine Cracking-Kosten, weil es nichts zu cracken gibt.

Passwortspeicherung sollte einwegig sein:

// Form des Ablaufs mit einer Passwort-Hashing-Library.
const hash = await argon2.hash(password);
const ok = await argon2.verify(hash, candidatePassword);

Normale Login-Systeme sollten das Passwort des Nutzers nie zurückgewinnen müssen. Sie müssen nur einen Kandidaten gegen einen gespeicherten Hash prüfen.

Realer Sicherheitsfehler 3: Basic Auth als Verschlüsselung missverstanden

Der Header sieht codiert genug aus, um Leute zu täuschen:

Authorization: Basic dXNlcjpwYXNzd29yZA==

Er decodiert zu:

atob("dXNlcjpwYXNzd29yZA==");
// "user:password"

HTTP Basic Auth ist nur akzeptabel, wenn der Request über HTTPS läuft. TLS verschlüsselt den HTTP-Austausch im Transit. Base64 packt username:password lediglich in einen header-tauglichen ASCII-String.

Über einfaches HTTP kann ein Netzwerkbeobachter die Credentials mitlesen. In Logs, Reverse Proxies, APM-Traces und Support-Dumps sollte ein Basic-Auth-Header wie ein Klartextpasswort behandelt werden.

Realer Sicherheitsfehler 4: JWT-Payloads als privat behandelt

Ein normales signiertes JWT hat drei Base64url-Abschnitte:

header.payload.signature

Die ersten beiden Abschnitte decodieren zu JSON. Der dritte ist die Signatur.

eyJzdWIiOiIxMjM0Iiwicm9sZSI6ImFkbWluIn0

Decodiert zu:

{
  "sub": "1234",
  "role": "admin"
}

Das heißt nicht, dass der Token gefälscht ist. Ein signiertes JWT kann manipulationsevident und trotzdem lesbar sein. Die Signatur schützt die Integrität: sie sagt dem Server, ob der Token von einer vertrauten Stelle ausgestellt wurde und ob Header oder Payload geändert wurden. Sie verbirgt die Payload nicht.

Keine Passwörter, keine API-Keys, keine Session-Secrets, keine vollständigen Kreditkartennummern, keine medizinischen Angaben und keine privaten Profildaten in eine gewöhnliche signierte JWT-Payload packen. Wenn Claims Vertraulichkeit brauchen, JWE verwenden oder die Daten gar nicht erst in den Token stecken.

Außerdem: Decodieren ist keine Verifizierung. Ein Entwickler-Tool kann Claims anzeigen; ein Server muss trotzdem Signatur, Issuer, Audience, Ablauf, Key ID, Algorithmus-Policy und applikationsspezifische Autorisierungsregeln prüfen, bevor er ihnen traut.

Realer Sicherheitsfehler 5: URL-Parameter, die opak aussehen

Alte Apps übergeben State oft so:

/profile?data=eyJ1c2VySWQiOjQyfQ==

Das decodiert zu:

{
  "userId": 42
}

Zwei Probleme folgen daraus. Jeder kann es lesen, und jeder kann es ändern und neu codieren. Wenn /profile?data=... allein entscheidet, welcher User-Datensatz geladen wird, ist der Bug fehlende Autorisierung.

Sichere Muster:

  • Decodierte URL-Daten als untrusted Input behandeln.
  • Berechtigungen serverseitig erneut prüfen.
  • Einen kurzen, zufälligen serverseitigen Referenzwert vorziehen, wenn der State sensibel ist.
  • Einen HMAC oder eine Signatur nutzen, wenn der Client manipulationsevidenten State tragen muss.
  • Privaten State aus URLs heraushalten, weil URLs in Logs, Browserverlauf, Analytics und Support-Screenshots landen.

Realer Sicherheitsfehler 6: „Verschlüsselte" Exporte, die nur codiert sind

Ein weiterer Code-Review-Geruch ist eine Funktion wie diese:

function exportEncryptedBackup(data) {
  return btoa(JSON.stringify(data));
}

Der Funktionsname sagt verschlüsselt. Der Code sagt codiert.

Wenn der Export nur ein portabler Text-Blob sein soll, umbenennen:

function exportBase64Backup(data) {
  return btoa(JSON.stringify(data));
}

Wenn Nutzern gesagt wird, der Export sei verschlüsselt, echte authentifizierte Verschlüsselung verwenden und das Key-Handling explizit machen. Das bedeutet: geprüfte Krypto-Library, zufällige Nonces/IVs, Authentication Tags, sichere Schlüsselerzeugung und ein Plan für die Schlüsselspeicherung. Base64 kann den Ciphertext danach immer noch verpacken.

Wofür Base64 tatsächlich gut ist

Base64 ist nützlich. Der Fehler ist, ihm eine Security-Aufgabe zuzuweisen.

Gute Verwendungen:

  • Binärdaten in JSON, XML, HTML oder E-Mail.
  • MIME-E-Mail-Anhänge.
  • Kleine data:-URIs für Bilder oder Schriften.
  • Formatierung kryptographischer Ausgaben: Ciphertext, Signaturen, Nonces, Salts und öffentliche Schlüssel.
  • JWT- und JOSE-Compact-Serialization-Segmente.
  • Opake Pagination-Cursor, wenn Lesbarkeit ein Produkt-Thema ist, keine Sicherheitsgrenze.
  • Debugging von API-Feldern, JWT-Payloads und Config-Blobs.

Die sichere Formulierung heißt „Base64-codiert", nicht „verschlüsselt". Dieses eine Wort verhindert erstaunlich viele Missverständnisse in der Produktion.

Was man stattdessen verwenden sollte

Wenn du zu Base64 gegriffen hast, weil du Sicherheit wolltest, wähle das Primitiv, das zur Aufgabe passt:

Ziel Stattdessen verwenden
Daten vor Nutzern oder Angreifern verbergen Authentifizierte Verschlüsselung wie AES-GCM oder XChaCha20-Poly1305
Daten während des Transports schützen HTTPS/TLS
Passwörter speichern Argon2id, bcrypt oder scrypt mit Salts pro Passwort und angepassten Kostenparametern
API-Keys speichern Secrets-Manager, serverseitige Umgebung, Scopes, Audit-Logs und Rotation
Beweisen, dass ein Wert nicht geändert wurde HMAC oder digitale Signatur
Nutzer daran hindern, URL-State zu editieren Serverseitige Autorisierung plus signierter State oder serverseitige Session-Referenz
JWT-Claims privat halten JWE, oder private Claims gar nicht erst in den Token packen
Binäres in JSON, E-Mail oder Header packen Base64 ist richtig

Der schwere Teil ist selten der Algorithmusname. Es sind Threat Modeling, Key Management, Rotation, Zugriffskontrolle und die Frage, wer den Klartext überhaupt sehen dürfen soll.

Eine praktische Code-Review-Checkliste

Base64-Nutzungen suchen:

rg "btoa\\(|atob\\(|base64|Buffer\\.from\\(.*base64|toString\\('base64'\\)"

Jeden Treffer einordnen:

  1. Transport: Bytes-zu-Text für JSON, MIME, Data URI, PEM oder Storage. Üblicherweise okay.
  2. Formatierung nach Krypto: Ciphertext, Nonce, Signatur, Salt oder Public Key. Üblicherweise okay, wenn die Krypto stimmt.
  3. Opakes Interface: Cursor, Invite-Code oder Client-getragener State. Autorisierung und Manipulationsschutz prüfen.
  4. Secret-Versteck: API-Key, Passwort, Token, Credential, privater Claim oder Nutzerdaten. Nicht okay.
  5. Irreführende Namen: Variablen oder UI-Texte sagen verschlüsselt, sicher, geschützt, geheim, maskiert oder verborgen. Namen oder Implementierung korrigieren.

Dann fragen:

  • Würden Browser, Mobile App, Nutzer, Log-Collector, Proxy oder Support-Tool den codierten Wert erhalten?
  • Steht direkt daneben ein Decoder?
  • Prüft ein Server die Berechtigungen nach dem Decodieren?
  • Ist der Wert signiert, wenn der Client ihn editieren kann?
  • Wenn es ein JWT ist: wird die Signatur vor jeder Vertrauensentscheidung verifiziert?
  • Wenn es ein Passwort ist: warum ist es überhaupt umkehrbar?

Das hält das Review geerdet. Ein PNG in einer Data URI ist langweilig. Ein Production-API-Key im JavaScript-Bundle nicht.

Incident Response: Wenn du Base64-„Secrets" findest

Wenn du ein Base64-codiertes Secret im Sourcecode, in Logs, einem Ticket, Analytics, einer URL oder einem Datenbank-Export findest:

  1. Lokal decodieren, um zu bestätigen, worum es sich handelt.
  2. Den decodierten Wert als exponiert behandeln.
  3. Credentials, Tokens und Schlüssel rotieren, die sichtbar gewesen sein könnten.
  4. Die codierte Kopie aus Source, Logs, Screenshots oder Tickets entfernen, wo möglich.
  5. Das Design durch serverseitiges Secret, gescopeden Public Key, signierten State, echte Verschlüsselung oder Passwort-Hash ersetzen.
  6. Eine Review-Regel ergänzen, damit das gleiche Muster nicht zurückkehrt.

Keine Zeit mit der Debatte verlieren, ob der Base64-String „schwer zu bemerken" war. Automatisierte Scanner, Browser-Nutzer und Angreifer können ihn alle decodieren.

Wenn du wirklich einen verschlüsselten Token brauchst

Ein Standard-signiertes JWT nutzt JWS: lesbare Payload, manipulationsevidenter Signatur. Wenn du echte Vertraulichkeit der Claims brauchst, verwende JWE (JSON Web Encryption).

Ein kompaktes JWE hat fünf Base64url-Abschnitte:

protected-header.encrypted-key.iv.ciphertext.authentication-tag

Diese Abschnitte sind weiterhin Base64url-Text. Die Vertraulichkeit kommt aus dem JWE-Verschlüsselungsalgorithmus und dem Schlüssel des Empfängers, nicht aus Base64url selbst.

In vielen Webanwendungen ist ein einfacheres Design besser: sensiblen State auf dem Server halten und dem Browser eine kurze, zufällige Session-ID schicken. Nicht jedes lesbare JWT muss ein JWE werden.

Häufige Fragen

Ist Base64 Verschlüsselung?

Nein. Base64 ist umkehrbare Codierung ohne geheimen Schlüssel. Wer den String hat, kann ihn mit einer Browserfunktion, einem Terminal-Befehl, einem Online-Tool oder ein paar Zeilen Code decodieren.

Ist Base64 wenigstens Obfuskation?

Nur im schwächsten Sinn. Vielleicht verbirgt es den Wert vor einem flüchtigen Blick, aber es wird keinen Entwickler, Angreifer, Scanner, keine Browser-Extension, keinen Log-Reader und kein Support-Tool aufhalten.

Ist Base64 sicher für Passwörter oder API-Keys?

Nein. Passwörter brauchen langsames Passwort-Hashing wie Argon2id, bcrypt oder scrypt. API-Keys gehören serverseitig oder in einen Secrets-Manager mit Scopes, Auditierbarkeit und Rotation.

Kann jeder eine JWT-Payload lesen?

Ja, bei gewöhnlichen signierten JWTs. Header und Payload sind Base64url-codiertes JSON. Die Signatur kann nach Verifikation die Integrität belegen, aber sie verbirgt die Claims nicht.

Warum ist Basic Auth Base64, wenn es nicht sicher ist?

Basic Auth nutzt Base64, um username:password in einen HTTP-Header zu packen. HTTPS/TLS liefert die Transportverschlüsselung. Ohne HTTPS sind Basic-Auth-Credentials im Netzwerk effektiv Klartext.

Ist Base64url sicherer als Base64?

Nein. Base64url ersetzt + und / durch - und _ und lässt oft das Padding weg, damit der Wert in URLs und JWTs funktioniert. Es ist trotzdem umkehrbare Codierung.

Was sollte ich statt Base64 zur Verschlüsselung verwenden?

Ein geprüftes authentifiziertes Verschlüsselungsverfahren wie AES-GCM, XChaCha20-Poly1305 oder JWE, plus saubere Schlüsselerzeugung, Schlüsselspeicherung, Rotation und Zugriffskontrolle. Kein eigenes Kryptoformat entwerfen.

Wofür ist Base64 eigentlich da?

Base64 stellt Bytes als Text dar: Binärfelder in JSON, E-Mail-Anhänge, Data URIs, PEM-Dateien, Signaturen, Formatierung von Ciphertext und URL-sichere Token-Segmente. Es ist ein Wrapper, keine Sicherheitsgrenze.

Im Browser codieren und decodieren

Musst du gerade jetzt einen Base64-String prüfen? Base64 Encode & Decode auf fixjson.org codiert und decodiert Text lokal im Browser, inklusive UTF-8-Inhalten. Nutze es, um JWT-Payloads, API-Response-Felder oder data:-URIs anzusehen, aber verwechsle Decodieren nicht mit Verifikation oder Entschlüsselung.

Verwandte Tools & Guides

Quellen

Zuletzt überprüft im Juli 2026.