„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:passwordmit 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:
- Transport: Bytes-zu-Text für JSON, MIME, Data URI, PEM oder Storage. Üblicherweise okay.
- Formatierung nach Krypto: Ciphertext, Nonce, Signatur, Salt oder Public Key. Üblicherweise okay, wenn die Krypto stimmt.
- Opakes Interface: Cursor, Invite-Code oder Client-getragener State. Autorisierung und Manipulationsschutz prüfen.
- Secret-Versteck: API-Key, Passwort, Token, Credential, privater Claim oder Nutzerdaten. Nicht okay.
- 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:
- Lokal decodieren, um zu bestätigen, worum es sich handelt.
- Den decodierten Wert als exponiert behandeln.
- Credentials, Tokens und Schlüssel rotieren, die sichtbar gewesen sein könnten.
- Die codierte Kopie aus Source, Logs, Screenshots oder Tickets entfernen, wo möglich.
- Das Design durch serverseitiges Secret, gescopeden Public Key, signierten State, echte Verschlüsselung oder Passwort-Hash ersetzen.
- 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
- Base64 Encode & Decode – Base64 lokal im Browser codieren und decodieren.
- Base64-Strings und JWT-Payloads decodieren – praktische Base64- und Base64url-Decodierung.
- Ein JWT decodieren – Claims lesen und verstehen, warum Decodieren keine Verifikation ist.
- Sensibles JSON und lokale Tools – entscheiden, was man bedenkenlos in Browser-Tools kleben kann.
- JSON Stringify – verschachtelte JSON-Strings prüfen und escapen.
- RFC 4648: Der Base64-Standard – der formale Hintergrund zu Base64 und Base64url.
- RFC 7519: JSON Web Token – JWT-Struktur und Sicherheitsüberlegungen.
Quellen
- RFC 4648 – Base64, Base64url, Padding, Alphabete und Sicherheitsüberlegungen.
- RFC 7617 – HTTP Basic Authentication und ihr Base64-Credential-Format.
- RFC 7519 – JWT-Struktur, Vertrauensentscheidungen und Datenschutz.
- RFC 7516 – JSON Web Encryption und die kompakte JWE-Serialisierung.
- MDN btoa und MDN atob – Browser-Base64-Primitive und UTF-8-Fallstricke.
- OWASP Cryptographic Storage Cheat Sheet – Verschlüsselung, authentifizierte Modi und Key-Management.
- OWASP Password Storage Cheat Sheet – Leitfaden zum Passwort-Hashing.
Zuletzt überprüft im Juli 2026.