- Base64-Codierung ist ein Verfahren, das Binärdaten in ASCII-Text umwandelt, um die Wahrscheinlichkeit zu verringern, dass Daten beim Speichern oder Übertragen falsch interpretiert werden
- Da es sich nicht um Verschlüsselung, sondern um eine Änderung der Darstellungsform handelt, lassen sich codierte Daten leicht wieder in den ursprünglichen Text oder die ursprünglichen Dateidaten zurückverwandeln
- 64 Zeichen lassen sich mit 6 Bit darstellen, daher enthält ein Base64-Zeichen 6 Bit Daten, und 3 Byte (24 Bit) werden in vier Base64-Zeichen umgewandelt
- Nützlich ist dies bei Data URLs in HTML, bei der Übertragung binärer Daten per E-Mail sowie in Umgebungen wie textzentrierten Netzwerken oder URLs, in denen rohe Binärdaten Probleme verursachen können
- Viele Sprachen und Tools bieten Funktionen zum Codieren und Decodieren, darunter Ruby, C#, PHP, JavaScript und der
base64-Befehl im Terminal
Was Base64 verändert
- Base64-Codierung wandelt Binärdaten in Text um, genauer gesagt in ASCII-Text
- Das Ergebnis verwendet nur die folgenden 64 Zeichen
A-Za-z0-9+/
- Dieser Zeichensatz dient als sicherer Zeichensatz, um Situationen zu vermeiden, in denen Zeichen wie
<,>,\nauf älteren Computern oder in Programmen falsch interpretiert werden - Wenn man
"Ruby on Rails"in Base64 codiert, erhält manUnVieSBvbiBSYWlscw== - Base64 ist keine Verschlüsselung
- Codierte Daten lassen sich leicht wieder in den ursprünglichen Text zurückverwandeln
- Es verbirgt keine Daten, sondern verändert nur deren Darstellung
Situationen, in denen Base64 verwendet wird
- Data URLs ermöglichen es, Dateidaten wie Bilder direkt in HTML einzubetten; dabei wird Base64-codierter Text verwendet
- Das Beispielformat lautet
data:[<mime type>][;charset=<charset>][;base64],<encoded data> - In E-Mails wird Base64 seit Langem verwendet, um Binärdaten auch in Umgebungen sicher zu transportieren, in denen Server Zeilenumbrüche verändern können
- Wenn Bilddaten direkt in den HTML-Quelltext eingefügt werden, ist eine Codierung nötig, damit Zeichen wie
<und>nicht als Tags interpretiert werden - Es kann auch verwendet werden, wenn Binärdaten über Netzwerke gespeichert oder übertragen werden sollen, die für die Verarbeitung von Text oder US-ASCII-Daten ausgelegt sind
- Base64 kann auch eingesetzt werden, um Daten zu übertragen, die Zeichen enthalten, die sich nur schwer in URLs einfügen lassen
- Codierungen der Base-Familie machen es möglich, Objekte mit Texteditoren zu bearbeiten, und werden deshalb in vielen Anwendungen eingesetzt
Codierungsalgorithmus
- Die Base64-Codierung läuft in der folgenden Reihenfolge ab
- Der Text wird in eine Binärdarstellung umgewandelt
- Die Bits werden in Gruppen zu je 6 Bit aufgeteilt
- Jede 6-Bit-Gruppe wird in eine Dezimalzahl von 0 bis 63 umgewandelt
- Diese Zahl wird in das entsprechende Zeichen des Base64-Alphabets überführt
- Wenn in der letzten Gruppe Bits fehlen, können
=oder==als Padding angehängt werden - Um 64 Zeichen darzustellen, werden 6 Bit benötigt
2^6 = 64- Eine Base64-Zahl steht für 6 Bit Daten
- Ein Byte besteht aus 8 Bit, und das kleinste gemeinsame Vielfache von 8 und 6 ist 24
- 24 Bit entsprechen 3 Byte
- 24 Bit werden als vier 6-Bit-Base64-Zahlen dargestellt
Beispiel für die Codierung von „Akshay“
"Akshay"ergibt nach der Umwandlung jedes Zeichens in ASCII-Zahlen und anschließend in Binärdarstellung Folgendes01000001 01101011 01110011 01101000 01100001 01111001
- Teilt man dies in 6-Bit-Gruppen auf, ergibt sich
010000 010110 101101 110011 011010 000110 000101 111001
- Wandelt man jede Gruppe in eine Dezimalzahl um, erhält man die folgenden Werte
16 22 45 51 26 6 5 57
- Im Base64-Alphabet entsprechen diesen die folgenden Zeichen
Q W t z a G F 5
- Daher lautet die Base64-Darstellung von
"Akshay"QWtzaGF5 - Auf dieselbe Weise lassen sich auch Dateien wie Bilder, PDFs, Texte oder Videos in Binärdaten umwandeln und dann mit Base64 codieren, um sie als ASCII-Text zu speichern oder zu übertragen
Verwendung in Sprachen und Tools
- Ruby verarbeitet Codierung und Decodierung mit dem Modul
Base64Base64.encode64("Ruby on Rails")Base64.decode64(encoded)
- In C# wird eine Zeichenkette zunächst in ein Byte-Array umgewandelt, dann mit
Convert.ToBase64Stringcodiert und mitSystem.Convert.FromBase64Stringdecodiert - PHP bietet die Top-Level-Funktionen
base64_encodeundbase64_decode - In JavaScript wird mit
btoa()codiert und mitatob()decodiert - Auch im Terminal lassen sich Codierung und Decodierung mit dem Befehl
base64ausführenecho "akshay" | base64gibtYWtzaGF5Cg==ausecho "YWtzaGF5Cg==" | base64 -dgibtakshayaus
1 Kommentare
Hacker-News-Kommentare
Danke, dass hier betont wird, dass dabei kein Text verschlüsselt wird. Viele Junior-Entwickler lernen den Unterschied zu spät und fallen darauf herein: Verschlüsselung lässt sich nur mit einem Geheimnis rückgängig machen, Hashing lässt sich nicht rückgängig machen, und Encoding lässt sich immer leicht rückgängig machen.
Es ist auch gut zu wissen, dass die Entropie dieselbe bleibt wie bei der Eingabe, selbst wenn die Ausgabe zufällig aussieht. Man sollte ein Passwort also nicht mit Base64 encodieren, um es stärker zu machen.
Wenn das Passwort vollständig zufällig erzeugt wurde, bringt Base64-Encoding nichts. Wenn es aber nach einem Schema mit niedriger Entropie erstellt wurde, etwa mit Wörterbuchwörtern oder leicht merkbaren Regeln, muss ein Angreifer seinen cleveren Passwort-Cracker so konfigurieren, dass er auch Base64-Encoding-Regeln berücksichtigt. Dadurch kommt pro Versuch immerhin eine zusätzliche Operation hinzu.
Natürlich sollte man solche Passwortschemata nicht verwenden. Ein Passwort im Stil von „correct horse battery staple“ halte ich für ausreichend.
Hashing hat neben Sicherheit viele weitere Zwecke, und deshalb gibt es auch viele verschiedene Hash-Bibliotheken. Wenn man Hashes für Sicherheits- oder Kryptozwecke verwendet, sollte man einen Hash nehmen, der genau dafür entwickelt wurde. CRC-Hashes sind zwar schnell, eignen sich aber nicht gut für Benutzerpasswörter.
Eine interessante Eigenschaft von Base64 ist, dass sich der Anfang des Ergebnisses, wenn man mit irgendeinem String beginnt und das Encoding wiederholt, immer mehr einem Fixpunkt annähert. Das lässt sich auch mit Bash überprüfen.
Ich habe das vor mehr als zehn Jahren zufällig entdeckt und kryptisch getwittert [1]. Jemand schrieb dazu einen Blogbeitrag und postete ihn auch hier, aber es gab kaum Diskussion [2]. Als jemand anderes es auf Reddit /r/compsci postete, entstand dort eine produktive Diskussion, die den Blogbeitrag korrigierte [3]. Der Blog ist inzwischen offline, aber im Internet Archive gibt es noch eine Kopie [4].
[1] https://twitter.com/p4bl0/status/298900842076045312
[2] https://news.ycombinator.com/item?id=5181256
[3] https://www.reddit.com/r/compsci/comments/18234a/the_base64_...
[4] https://web.archive.org/web/20130315082932/http://fmota.eu/b...
Beim Encodieren in Bash sollte man die Option
-nverwenden:$ echo -n "abcde" |base64Ohne
-nfügtechoam Ende des Strings ein Zeilenumbruchzeichen hinzu, und dieses Zeichen wird ebenfalls encodiert.echoverwenden. https://linux.die.net/man/1/printfEs gibt auch base64URL, das für URLs sichere andere ASCII-Zeichen für das Encoding verwendet. Manche Entwickler nennen BASE64URL einfach base64, was für Leute, die das nicht wissen, zu Problemen führen kann.
https://datatracker.ietf.org/doc/html/rfc4648#section-5
~und.keine Wortzeichen sind; wenn man also einen encodierten Wert doppelklickt, wird nicht der ganze Wert ausgewählt. In vielen Fällen, in denen man kopieren und einfügen will, ist das unnötige Reibung.Base62-Encoding (
0-9A-Za-z) ist fast so effizient wie base64url und bleibt URL-sicher, ist aber leichter per Copy-and-paste zu handhaben. Wenn man Mehrdeutigkeiten beim Lesen durch Menschen reduzieren will, kann man auf Base58 heruntergehen; meist sind BaseXX-Encodings aber ohnehin so lang, dass Copy-and-paste üblich ist, daher ist das kein großes Problem.https://en.wikipedia.org/wiki/Base62
Ein Base64-String mit Padding hat immer eine Länge, die ein Vielfaches von 4 ist. Wenn man also einen String erhält, dessen Länge kein Vielfaches von 4 ist, kann man erkennen, wie viel Padding ursprünglich nötig gewesen wäre, und auch entscheiden, wie die letzten 3 Bytes zu decodieren sind.
Deshalb ist mir etwas unklar, warum Base64 überhaupt
==-Padding braucht.Immer wenn es um Umrechnung zwischen Zahlensystemen geht, bewerbe ich schamlos meinen Konverter für beliebige Basen: https://convert.zamicol.com
base64 unter „useful alphabets“ ist das „natürliche“ Zahlensystem, bei dem wiederholt durch die Basis dividiert wird; die „Bucket“-Umwandlungsmethode aus dem RFC findet sich unter extras.
Wenn etwas encodiert wird und von Menschen manuell eingetippt werden muss, empfehle ich https://en.wikipedia.org/wiki/Base32
Kaum etwas ist nerviger als schlechte Fonts, bei denen man nicht weiß, ob es
loder1ist, odero,Ooder0.Etwas genauer gesagt: Bei Base64 sollte man eher sagen, dass Binärdaten in eine Teilmenge von ASCII encodiert werden, nicht in den gesamten ASCII-Zeichensatz.
ASCII hat 128 Codepoints, davon 95 druckbare Zeichen und 33 Steuerzeichen. Base64 verwendet davon nur 64, beziehungsweise 65 einschließlich Padding.
Im Artikel wurde der Zweck des Paddings
=/==nicht ausführlich behandelt, und es wurde auch nicht anhand eines Beispiels gezeigt, wie Daten verarbeitet werden, die sich nicht exakt in 6-Bit-Gruppen aufteilen lassen.Ich glaube, ich habe es grob verstanden, möchte es aber genau wissen. Es wäre gut, wenn jemand kurz und vollständig beantworten könnte, wann
=und wann==verwendet wird, ob es immer angehängt wird oder es auch Fälle ohne gibt, wie die übrig bleibenden Bits bei einem String wie"5byte"genau behandelt werden und worauf man beim Decoding achten muss.Ein Base64-Zeichen steht für 6 Bit, daher entspricht ein 3-Byte-Block von Daten einem Block aus 4 Base64-codierten Zeichen. Deshalb lassen sich Base64-Daten bequem in Vierergruppen verarbeiten.
=ist Padding, das je nach Bedarf 0-, 1- oder 2-mal angehängt wird, um die Länge des codierten Strings auf ein Vielfaches von 4 zu bringen. Zum Beispiel wird"543210"zu"543210==","6543210"zu"6543210=", und"76543210"braucht kein Padding. Es gibt keinen Fall, in dem drei=als Padding nötig wären, weil schon 1 Byte Daten mindestens 2 Base64-Zeichen benötigt.Übrig bleibende Bits kann man mit 0 auffüllen, und der Decoder kann erkennen, dass nicht genug Bits vorhanden sind, um ein vollständiges Byte zu bilden, und sie verwerfen. In den meisten modernen Fällen ist Padding eher Konvention als zwingend erforderlich. Der Wikipedia-Artikel ist recht ausführlich: https://en.wikipedia.org/wiki/Base64
Padding-Zeichen am Ende eines Streams, einer Datei oder eines Strings lassen sich aus der bereits verarbeiteten Länge ableiten und sind streng genommen nicht zwingend erforderlich.
Allerdings ist die Behandlung von Padding ziemlich subtil, und genau diese Unterschiede haben interessante Implementierungsvarianten hervorgebracht: https://eprint.iacr.org/2022/361.pdf
Letztlich wird also in 24-Bit-Einheiten codiert. Wenn die Daten enden, wird der verbleibende Teil der 24 Bit mit
=statt mitAaufgefüllt, weilAals Datenwert000000bedeutet. Ich habe das Ganze auch zweimal gelesen, um es zu verstehen.Mein Base64-Encoder-Shader ist hier: https://github.com/Rezmason/excel_97_egg/blob/main/glsl/base...
Ich habe ihn auf etwa 13 Zeilen GLSL reduziert: https://github.com/Rezmason/excel_97_egg/blob/main/glsl/base...
Er wird im Cursed Mode eines Nebenprojekts verwendet und rendert einen WebGL-Framebuffer etwa 15-mal pro Sekunde als Base64-codierte 640x480-Pixel-Indexfarben-BMP: https://rezmason.github.io/excel_97_egg/?cursed=1
Wenn man tiefer einsteigt, gibt es weitere interessante Details, und die Varianten dieser Details sind erstaunlich zahlreich.
Wenn die Länge der Eingabedaten nicht genau ein Vielfaches von 3 Byte ist, werden für das Codieren der letzten 1 oder 2 Byte 2 bzw. 3 Base64-Zeichen verwendet. Da ein Base64-Zeichen 6 Bit darstellt, verwendet man 12 bzw. 18 Bit, um 8 bzw. 16 Bit darzustellen; dadurch bleiben 4 bzw. 2 zusätzliche Bits übrig, die nichts codieren.
Der RFC verlangt, dass Encoder diese Bits auf 0 setzen, sagt aber nur, dass Decoder Eingaben ablehnen dürfen, bei denen diese Bits nicht 0 sind. In der Praxis lehnen das standardmäßig kaum Implementierungen ab; soweit ich weiß, können nur Ruby, Rust und Go so konfiguriert werden, dass sie bei solchen Eingaben fehlschlagen. Python hat zwar eine Option
validate, prüft diese Bits aber nicht.Ein weiterer großer Unterschied ist die Behandlung von Whitespace und Nicht-Base64-Zeichen. Überraschend viele Implementierungen, darunter Python, ignorieren beliebige Zeichen in der Eingabe stillschweigend. Wenn man das Alphabet falsch wählt, wird das zum Problem: In Python etwa erzeugt
base64.standard_b64decode(base64.urlsafe_b64encode(b'\xFF\xFE\xFD\xFC'))keinen Fehler, sondern stillschweigend eine falsche Ausgabe.Interessant ist auch, dass Rubys Base64-Encoder alle 60 Zeichen einen Zeilenumbruch einfügt. Abgesehen von PEM gibt es keine Standardcodierung, die so kurze Zeilen verlangt, und PEM verlangt genau 64-Zeichen-Zeilen; daher ist das eine ziemlich eigenartige Wahl.
Ich habe einen Artikel geschrieben, der Unterschiede zwischen Programmiersprachen und einigen JavaScript-Bibliotheken zusammenfasst [1], und arbeite außerdem daran, besseres Base64 zu JS hinzuzufügen [2].
[1] https://gist.github.com/bakkot/16cae276209da91b652c2cb3f612a...
[2] https://github.com/tc39/proposal-arraybuffer-base64