Warum erfordert die Base64-Codierung ein Auffüllen, wenn die Eingabelänge nicht durch 3 teilbar ist?

99

Was ist der Zweck des Auffüllens in der Base64-Codierung? Das Folgende ist der Auszug aus Wikipedia:

"Es wird ein zusätzliches Pad-Zeichen zugewiesen, das verwendet werden kann, um die codierte Ausgabe in ein ganzzahliges Vielfaches von 4 Zeichen zu zwingen (oder äquivalent, wenn der nicht codierte Binärtext kein Vielfaches von 3 Bytes ist). Diese Auffüllzeichen müssen dann beim Decodieren verworfen werden Ermöglichen Sie weiterhin die Berechnung der effektiven Länge des nicht codierten Texts, wenn seine binäre Eingangslänge nicht ein Vielfaches von 3 Bytes wäre (das letzte Nicht-Pad-Zeichen wird normalerweise so codiert, dass der letzte 6-Bit-Block, den es darstellt, Null ist - auf seine niedrigstwertigen Bits aufgefüllt, dürfen höchstens zwei Pad-Zeichen am Ende des codierten Streams auftreten). "

Ich habe ein Programm geschrieben, das base64 jede Zeichenfolge codieren und jede base64-codierte Zeichenfolge decodieren kann. Welches Problem löst die Polsterung?

Anand Patel
quelle

Antworten:

207

Ihre Schlussfolgerung, dass Polsterung nicht erforderlich ist, ist richtig. Es ist immer möglich, die Länge der Eingabe eindeutig aus der Länge der codierten Sequenz zu bestimmen.

Das Auffüllen ist jedoch in Situationen nützlich, in denen Base64-codierte Zeichenfolgen so verkettet sind, dass die Längen der einzelnen Sequenzen verloren gehen, wie dies beispielsweise in einem sehr einfachen Netzwerkprotokoll der Fall sein kann.

Wenn ungepolsterte Zeichenfolgen verkettet sind, können die Originaldaten nicht wiederhergestellt werden, da Informationen über die Anzahl der ungeraden Bytes am Ende jeder einzelnen Sequenz verloren gehen. Wenn jedoch aufgefüllte Sequenzen verwendet werden, gibt es keine Mehrdeutigkeit, und die Sequenz als Ganzes kann korrekt decodiert werden.

Bearbeiten: Eine Illustration

Angenommen, wir haben ein Programm, das Wörter mit base64 codiert, verkettet und über ein Netzwerk sendet. Es codiert "I", "AM" und "TJM", fügt die Ergebnisse ohne Auffüllen zusammen und überträgt sie.

  • Icodiert zu SQ( SQ==mit Polsterung)
  • AMcodiert zu QU0( QU0=mit Polsterung)
  • TJMcodiert zu VEpN( VEpNmit Polsterung)

Die übertragenen Daten sind also SQQU0VEpN. Der Empfänger base64-decodiert dies I\x04\x14\xd1Q)anstelle der beabsichtigten IAMTJM. Das Ergebnis ist Unsinn, weil der Absender Informationen darüber zerstört hat, wo jedes Wort in der codierten Sequenz endet . Wenn der Absender SQ==QU0=VEpNstattdessen gesendet hätte, hätte der Empfänger dies als drei separate base64-Sequenzen dekodieren können, die sich verketten würden, um zu geben IAMTJM.

Warum sich mit Polsterung beschäftigen?

Warum nicht einfach das Protokoll so gestalten, dass jedem Wort eine ganzzahlige Länge vorangestellt wird? Dann könnte der Empfänger den Stream korrekt dekodieren und es wäre kein Auffüllen erforderlich.

Das ist eine großartige Idee, solange wir die Länge der Daten kennen, die wir codieren, bevor wir mit der Codierung beginnen. Aber was wäre, wenn wir anstelle von Worten Videostücke von einer Live-Kamera codieren würden? Wir kennen möglicherweise nicht die Länge jedes Stücks im Voraus.

Wenn das Protokoll eine Auffüllung verwenden würde, wäre es überhaupt nicht erforderlich, eine Länge zu übertragen. Die Daten könnten codiert werden, wenn sie von der Kamera eingehen, wobei jeder Block mit Auffüllen abgeschlossen wird, und der Empfänger könnte den Stream korrekt decodieren.

Natürlich ist das ein sehr ausgeklügeltes Beispiel, aber vielleicht zeigt es, warum Polsterung in manchen Situationen möglicherweise hilfreich ist.

TJM
quelle
22
+1 Die einzige Antwort, die neben "weil wir Ausführlichkeit und Redundanz aus unerklärlichen Gründen mögen" tatsächlich eine vernünftige Antwort liefert.
Ungültiger
1
Dies funktioniert einwandfrei für Chunks, die eindeutig codiert sind, von denen jedoch erwartet wird, dass sie nach dem Decodieren untrennbar verkettet sind. Wenn Sie U0FNSQ == QU0 = senden, können Sie den Satz rekonstruieren, verlieren jedoch die Wörter, aus denen der Satz besteht. Besser als nichts, denke ich. Insbesondere verarbeitet das GNU base64-Programm automatisch verkettete Codierungen.
Marcelo Cantos
2
Was wäre, wenn die Länge der Wörter ein Vielfaches von 3 wäre? Diese dumme Art der Verkettung zerstört Informationen (Wortenden), nicht das Entfernen von Auffüllungen.
GreenScape
2
Mit der Base64-Verkettung können Encoder große Chunks parallel verarbeiten, ohne die Chunk-Größen auf ein Vielfaches von drei ausrichten zu müssen. In ähnlicher Weise könnte es als Implementierungsdetail einen Encoder geben, der einen internen Datenpuffer mit einer Größe leeren muss, die kein Vielfaches von drei ist.
Andre D
1
Diese Antwort könnte Sie denken lassen, dass Sie etwas wie "SQ == QU0 = VEpN" dekodieren können, indem Sie es einfach einem Decoder geben. Eigentlich scheint es nicht möglich zu sein, zum Beispiel unterstützen die Implementierungen in Javascript und PHP dies nicht. Beginnend mit einer verketteten Zeichenfolge müssen Sie entweder 4 Bytes gleichzeitig dekodieren oder die Zeichenfolge nach dem Auffüllen von Zeichen teilen. Es scheint, als würden diese Implementierungen die Füllzeichen einfach ignorieren, selbst wenn sie sich in der Mitte eines Strings befinden.
Roman
38

In diesem Zusammenhang finden Sie hier einen Basiskonverter für eine beliebige Basiskonvertierung, den ich für Sie erstellt habe. Genießen! https://convert.zamicol.com/

Was sind Polsterzeichen?

Füllzeichen erfüllen die Längenanforderungen und haben keine Bedeutung.

Dezimalbeispiel für das Auffüllen: Angesichts der willkürlichen Anforderung, dass alle Zeichenfolgen 8 Zeichen lang sind, kann die Zahl 640 diese Anforderung erfüllen, indem vorhergehende Nullen als Auffüllzeichen verwendet werden, da sie keine Bedeutung haben, "00000640".

Binäre Codierung

Das Byte-Paradigma: Das Byte ist die De-facto-Standardmaßeinheit, und jedes Codierungsschema muss sich auf Bytes beziehen.

Base256 passt genau in dieses Paradigma. Ein Byte entspricht einem Zeichen in base256.

Base16 , hexadezimal oder hexadezimal, verwendet 4 Bits für jedes Zeichen. Ein Byte kann zwei base16-Zeichen darstellen.

Base64 passt im Gegensatz zu base256 und base16 nicht gleichmäßig in das Byte-Paradigma (und base32 auch nicht). Alle base64-Zeichen können in 6 Bit dargestellt werden, 2 Bit vor einem vollständigen Byte.

Wir können die Base64-Codierung gegenüber dem Byte-Paradigma als Bruch darstellen: 6 Bits pro Zeichen über 8 Bits pro Byte . Dieser Bruchteil wurde um 3 Bytes über 4 Zeichen reduziert.

Dieses Verhältnis, 3 Bytes pro 4 Base64-Zeichen, ist die Regel, die wir beim Codieren von Base64 befolgen möchten. Die Base64-Codierung kann nur das Messen mit 3-Byte-Bündeln versprechen, im Gegensatz zu base16 und base256, bei denen jedes Byte für sich stehen kann.

Also , warum ist Polsterung ermutigt , obwohl Codierung sehr gut ohne die Füllzeichen funktionieren könnte?

Wenn die Länge eines Streams unbekannt ist oder wenn es hilfreich sein kann, genau zu wissen, wann ein Datenstrom endet, verwenden Sie Padding. Die Füllzeichen weisen ausdrücklich darauf hin, dass diese zusätzlichen Stellen leer sein sollten, und schließen Mehrdeutigkeiten aus. Selbst wenn die Länge beim Auffüllen unbekannt ist, wissen Sie, wo Ihr Datenstrom endet.

Als Gegenbeispiel erlauben einige Standards wie JOSE das Auffüllen von Zeichen nicht. In diesem Fall funktioniert eine kryptografische Signatur nicht, wenn etwas fehlt, oder es fehlen andere Nicht-Base64-Zeichen (wie das "."). Obwohl keine Annahmen über die Länge getroffen werden, ist keine Polsterung erforderlich, da etwas einfach nicht funktioniert, wenn etwas nicht stimmt.

Und genau das sagt der base64 RFC:

Unter bestimmten Umständen ist die Verwendung von Auffüllen ("=") in basenkodierten Daten nicht erforderlich oder wird nicht verwendet. Im allgemeinen Fall, wenn keine Annahmen über die Größe der transportierten Daten getroffen werden können, ist ein Auffüllen erforderlich, um korrekte decodierte Daten zu erhalten.

[...]

Der Auffüllschritt in Basis 64 [...] führt bei unsachgemäßer Implementierung zu nicht signifikanten Änderungen der codierten Daten. Wenn die Eingabe beispielsweise nur ein Oktett für eine Basis-64-Codierung ist, werden alle sechs Bits des ersten Symbols verwendet, aber nur die ersten zwei Bits des nächsten Symbols. Diese Pad-Bits MÜSSEN durch konforme Encoder auf Null gesetzt werden, was in den Beschreibungen zum Auffüllen unten beschrieben wird. Wenn diese Eigenschaft nicht gilt, gibt es keine kanonische Darstellung von basenkodierten Daten, und mehrere basenkodierte Zeichenfolgen können in dieselben Binärdaten decodiert werden. Wenn diese Eigenschaft (und andere in diesem Dokument beschriebene) gilt, ist eine kanonische Codierung garantiert.

Durch das Auffüllen können wir die Base64-Codierung mit dem Versprechen dekodieren, dass keine Bits verloren gehen. Ohne Auffüllen gibt es keine explizite Bestätigung mehr, dass in drei Byte-Bündeln gemessen wird. Ohne Auffüllen können Sie möglicherweise keine exakte Reproduktion der Originalcodierung ohne zusätzliche Informationen garantieren, die normalerweise von einer anderen Stelle in Ihrem Stapel stammen, z. B. TCP, Prüfsummen oder andere Methoden.

Beispiele

Hier ist das Beispielformular RFC 4648 ( http://tools.ietf.org/html/rfc4648#section-8 )

Jedes Zeichen in der Funktion "BASE64" verwendet ein Byte (base256). Wir übersetzen das dann in base64.

BASE64("")       = ""           (No bytes used. 0%3=0.)
BASE64("f")      = "Zg=="       (One byte used. 1%3=1.)
BASE64("fo")     = "Zm8="       (Two bytes. 2%3=2.)
BASE64("foo")    = "Zm9v"       (Three bytes. 3%3=0.)
BASE64("foob")   = "Zm9vYg=="   (Four bytes. 4%3=1.)
BASE64("fooba")  = "Zm9vYmE="   (Five bytes. 5%3=2.)
BASE64("foobar") = "Zm9vYmFy"   (Six bytes. 6%3=0.)

Hier ist ein Encoder, mit dem Sie herumspielen können: http://www.motobit.com/util/base64-decoder-encoder.asp

Zamicol
quelle
16
-1 Es ist ein schöner und gründlicher Beitrag darüber, wie Zahlensysteme funktionieren, aber es erklärt nicht, warum Padding verwendet wird, wenn die Codierung ohne perfekt funktionieren würde.
Matti Virkkunen
2
Hast du die Frage überhaupt gelesen? Sie müssen nicht brauchen padding richtig zu entschlüsseln.
Navin
3
Ich denke, diese Antwort hat tatsächlich den hier angegebenen Grund erklärt: "Ohne zusätzliche Informationen können wir keine exakte Reproduktion der Originalcodierung mehr garantieren." Es ist wirklich einfach, die Polsterung lässt uns wissen, dass wir die vollständige Codierung erhalten haben. Jedes Mal, wenn Sie 3 Bytes haben, können Sie davon ausgehen, dass es in Ordnung ist, es zu dekodieren. Machen Sie sich keine Sorgen, hum ... vielleicht wird ein weiteres Byte kommen und möglicherweise die Codierung ändern.
Didier A.
@DidierA. Woher wissen Sie, dass ein base64-Teilstring keine 3 weiteren Bytes mehr enthält? Zum Dekodieren von a char*benötigen Sie entweder die Größe der Zeichenfolge oder einen Nullterminator. Polsterung ist überflüssig. Daher die Frage von OP.
Navin
4
@Navin Wenn Sie die Base64-Bytes im Stream dekodieren, kennen Sie die Länge nicht. Mit der 3-Byte-Auffüllung wissen Sie, dass Sie jedes Mal, wenn Sie 3 Bytes haben, die 4 Zeichen verarbeiten können, bis Sie das Ende des Streams erreichen. Ohne sie müssen Sie möglicherweise zurückverfolgen, da das nächste Byte dazu führen kann, dass sich das vorherige Zeichen ändert. Daher können Sie nur dann sicher sein, dass Sie es richtig dekodiert haben, wenn Sie das Ende des Streams erreicht haben. Es ist also nicht sehr nützlich, aber es gibt einige Randfälle, in denen Sie es möglicherweise möchten.
Didier A.
1

Es hat heutzutage nicht viel Nutzen. Betrachten wir dies als eine Frage des ursprünglichen historischen Zwecks.

Die Base64-Codierung erscheint erstmals in RFC 1421 vom Jahr 1993. Diese RFC konzentriert sich eigentlich auf die Verschlüsselung von E-Mails, und Base64 wird in einem kleinen Abschnitt 4.3.2.4 beschrieben .

Dieser RFC erklärt nicht den Zweck der Auffüllung. Am nächsten an der Erwähnung des ursprünglichen Zwecks ist dieser Satz:

Ein vollständiges Codierungsquantum wird immer am Ende einer Nachricht abgeschlossen.

Es wird weder eine Verkettung (Top-Antwort hier) noch eine einfache Implementierung als expliziter Zweck für das Auffüllen vorgeschlagen. In Anbetracht der gesamten Beschreibung ist es jedoch nicht unangemessen anzunehmen, dass dies dem Decoder helfen soll, die Eingabe in 32-Bit-Einheiten ( "Quanten" ) zu lesen . Dies ist heute nicht mehr von Vorteil, jedoch hätte ein unsicherer C-Code 1993 diese Eigenschaft höchstwahrscheinlich tatsächlich ausgenutzt.

Roman Starkov
quelle
1
Ohne Auffüllen würde ein Versuch, zwei Zeichenfolgen zu verketten, wenn die Länge der ersten Zeichenfolge nicht ein Vielfaches von drei ist, häufig eine scheinbar gültige Zeichenfolge ergeben, aber der Inhalt der zweiten Zeichenfolge würde falsch dekodieren. Durch Hinzufügen der Polsterung wird sichergestellt, dass dies nicht auftritt.
Supercat
1
@supercat Wenn das das Ziel wäre, wäre es nicht einfacher, jede base64-Zeichenfolge mit nur einem einzigen "=" zu beenden? Die durchschnittliche Länge wäre kürzer und würde dennoch fehlerhafte Verkettungen verhindern.
Roman Starkov
2
Die durchschnittliche Länge von b'Zm9vYmFyZm9vYg==' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy' b'Zm9vYmFyZm9vYmFyZg==' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v' ist die gleiche wie die von b'Zm9vYmFyZm9vYg=' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy=' b'Zm9vYmFyZm9vYmFyZg=' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v='
Scott