Base64-Codierung und -Decodierung in Java 8

Java 8 wird hauptsächlich für die Einführung von Lambdas, Streams, einem neuen Datums- / Zeitmodell und der Nashorn-JavaScript-Engine in Java in Erinnerung bleiben. Einige werden sich auch an Java 8 erinnern, um verschiedene kleine, aber nützliche Funktionen wie die Base64-API einzuführen. Was ist Base64 und wie verwende ich diese API? Dieser Beitrag beantwortet diese Fragen.

Was ist Base64?

Base64 ist ein Binär-Text-Codierungsschema, das Binärdaten in einem druckbaren ASCII-Zeichenfolgenformat darstellt, indem es in eine Radix-64-Darstellung übersetzt wird. Jede Base64-Ziffer repräsentiert genau 6 Bits Binärdaten.

Base64-Anforderung für Kommentardokumente

Base64 wurde erstmals in RFC 1421: Datenschutzverbesserung für E-Mail im Internet beschrieben (aber nicht benannt): Teil I: Verfahren zur Verschlüsselung und Authentifizierung von Nachrichten. Später wurde es offiziell als Base64 in RFC 2045: Mehrzweck-Internet-Mail-Erweiterungen (MIME) Teil 1: Format von Internet-Nachrichtenkörpern vorgestellt und anschließend in RFC 4648: Die Datencodierungen Base16, Base32 und Base64 überarbeitet.

Base64 wird verwendet, um zu verhindern, dass Daten während der Übertragung durch Informationssysteme wie E-Mail geändert werden, die möglicherweise nicht 8-Bit-sauber sind (sie können 8-Bit-Werte beschädigen). Sie hängen beispielsweise ein Bild an eine E-Mail-Nachricht an und möchten, dass das Bild am anderen Ende ankommt, ohne verstümmelt zu werden. Ihre E-Mail-Software Base64-codiert das Bild und fügt den entsprechenden Text in die Nachricht ein, wie unten dargestellt:

Content-Disposition: inline; filename=IMG_0006.JPG Content-Transfer-Encoding: base64 /9j/4R/+RXhpZgAATU0AKgAAAAgACgEPAAIAAAAGAAAAhgEQAAIAAAAKAAAAjAESAAMAAAABAAYA AAEaAAUAAAABAAAAlgEbAAUAAAABAAAAngEoAAMAAAABAAIAAAExAAIAAAAHAAAApgEyAAIAAAAU AAAArgITAAMAAAABAAEAAIdpAAQAAAABAAAAwgAABCRBcHBsZQBpUGhvbmUgNnMAAAAASAAAAAEA ... NOMbnDUk2bGh26x2yiJcsoBIrvtPe3muBbTRGMdeufmH+Nct4chUXpwSPk/qK9GtJRMWWVFbZ0JH I4rf2dkZSbOjt7hhEzwcujA4I7Gust75pYVwAPpXn+kzNLOVYD7xFegWEKPkHsM/pU1F0NKbNS32 o24sSCOlaaFYLUhjky4x9PSsKL5bJsdWkAz3xirH2dZLy1DM2C44zx1FZqL2PTXY/9k=

Die Abbildung zeigt, dass dieses codierte Bild mit beginnt /und mit endet =. Das ...zeigt Text an, den ich der Kürze halber nicht gezeigt habe. Beachten Sie, dass die gesamte Codierung für dieses oder ein anderes Beispiel etwa 33 Prozent größer ist als die ursprünglichen Binärdaten.

Die E-Mail-Software des Empfängers decodiert das codierte Textbild mit Base64, um das ursprüngliche Binärbild wiederherzustellen. In diesem Beispiel wird das Bild inline mit dem Rest der Nachricht angezeigt.

Base64-Codierung und -Decodierung

Base64 basiert auf einfachen Codierungs- und Decodierungsalgorithmen. Sie arbeiten mit einer 65-stelligen Teilmenge von US-ASCII, wobei jedes der ersten 64 Zeichen einer äquivalenten 6-Bit-Binärsequenz zugeordnet ist. Hier ist das Alphabet:

Value Encoding Value Encoding Value Encoding Value Encoding 0 A 17 R 34 i 51 z 1 B 18 S 35 j 52 0 2 C 19 T 36 k 53 1 3 D 20 U 37 l 54 2 4 E 21 V 38 m 55 3 5 F 22 W 39 n 56 4 6 G 23 X 40 o 57 5 7 H 24 Y 41 p 58 6 8 I 25 Z 42 q 59 7 9 J 26 a 43 r 60 8 10 K 27 b 44 s 61 9 11 L 28 c 45 t 62 + 12 M 29 d 46 u 63 / 13 N 30 e 47 v 14 O 31 f 48 w (pad) = 15 P 32 g 49 x 16 Q 33 h 50 y

Das 65. Zeichen ( =) wird verwendet, um Base64-codierten Text auf eine ganzzahlige Größe aufzufüllen, wie in Kürze erläutert.

Teilmengeneigenschaft

Diese Teilmenge hat die wichtige Eigenschaft, dass sie in allen Versionen von ISO 646, einschließlich US-ASCII, identisch dargestellt wird und alle Zeichen in der Teilmenge auch in allen Versionen von EBCDIC identisch dargestellt werden.

Der Codierungsalgorithmus empfängt einen Eingabestream von 8-Bit-Bytes. Es wird angenommen, dass dieser Stream zuerst mit dem höchstwertigen Bit geordnet wird: Das erste Bit ist das höherwertige Bit im ersten Byte, das achte Bit ist das niederwertige Bit in diesem Byte und so weiter.

Von links nach rechts sind diese Bytes in 24-Bit-Gruppen organisiert. Jede Gruppe wird als vier verkettete 6-Bit-Gruppen behandelt. Jede 6-Bit-Gruppe indiziert in ein Array der 64 druckbaren Zeichen. Das resultierende Zeichen wird ausgegeben.

Wenn am Ende der zu codierenden Daten weniger als 24 Bit verfügbar sind, werden (rechts) Nullbits hinzugefügt, um eine ganzzahlige Anzahl von 6-Bit-Gruppen zu bilden. Dann können ein oder zwei =Pad-Zeichen ausgegeben werden. Es sind zwei Fälle zu berücksichtigen:

  • Ein verbleibendes Byte: An dieses Byte werden vier Nullbits angehängt, um zwei 6-Bit-Gruppen zu bilden. Jede Gruppe indiziert das Array und ein resultierendes Zeichen wird ausgegeben. Nach diesen beiden Zeichen werden zwei =Pad-Zeichen ausgegeben.
  • Zwei verbleibende Bytes: Zwei Nullbits werden an das zweite Byte angehängt, um drei 6-Bit-Gruppen zu bilden. Jede Gruppe indiziert das Array und ein resultierendes Zeichen wird ausgegeben. Nach diesen drei Zeichen wird ein =Pad-Zeichen ausgegeben.

Betrachten wir drei Beispiele, um zu erfahren, wie der Codierungsalgorithmus funktioniert. Angenommen, wir möchten Folgendes codieren @!*:

Source ASCII bit sequences with prepended 0 bits to form 8-bit bytes: @ ! * 01000000 00100001 00101010 Dividing this 24-bit group into four 6-bit groups yields the following: 010000 | 000010 | 000100 | 101010 These bit patterns equate to the following indexes: 16 2 4 42 Indexing into the Base64 alphabet shown earlier yields the following encoding: QCEq

Wir werden fortfahren, indem wir die Eingabesequenz auf Folgendes verkürzen @!:

Source ASCII bit sequences with prepended 0 bits to form 8-bit bytes: @ ! 01000000 00100001 Two zero bits are appended to make three 6-bit groups: 010000 | 000010 | 000100 These bit patterns equate to the following indexes: 16 2 4 Indexing into the Base64 alphabet shown earlier yields the following encoding: QCE An = pad character is output, yielding the following final encoding: QCE=

Das letzte Beispiel verkürzt die Eingabesequenz auf @:

Source ASCII bit sequence with prepended 0 bits to form 8-bit byte: @ 01000000 Four zero bits are appended to make two 6-bit groups: 010000 | 000000 These bit patterns equate to the following indexes: 16 0 Indexing into the Base64 alphabet shown earlier yields the following encoding: QA Two = pad characters are output, yielding the following final encoding: QA==

Der Decodierungsalgorithmus ist die Umkehrung des Codierungsalgorithmus. Es ist jedoch kostenlos, geeignete Maßnahmen zu ergreifen, wenn ein Zeichen erkannt wird, das nicht im Base64-Alphabet enthalten ist, oder eine falsche Anzahl von Pad-Zeichen.

Base64-Varianten

Es wurden mehrere Base64-Varianten entwickelt. Einige Varianten erfordern, dass der codierte Ausgabestream in mehrere Zeilen fester Länge unterteilt wird, wobei jede Zeile eine bestimmte Längenbeschränkung nicht überschreitet und (mit Ausnahme der letzten Zeile) über ein Zeilentrennzeichen (Wagenrücklauf \rgefolgt von einem Zeilenvorschub ) von der nächsten Zeile getrennt wird \n). Ich beschreibe die drei Varianten, die von der Base64-API von Java 8 unterstützt werden. Eine vollständige Liste der Varianten finden Sie im Wikipedia-Eintrag Base64.

Basic

RFC 4648 beschreibt eine Base64-Variante, die als Basic bekannt ist . Diese Variante verwendet das in Tabelle 1 von RFC 4648 und RFC 2045 (und weiter oben in diesem Beitrag gezeigte) dargestellte Base64-Alphabet zum Codieren und Decodieren. Der Codierer behandelt den codierten Ausgabestream als eine Zeile. Es werden keine Zeilentrennzeichen ausgegeben. Der Decoder lehnt eine Codierung ab, die Zeichen außerhalb des Base64-Alphabets enthält. Beachten Sie, dass diese und andere Bestimmungen überschrieben werden können.

MIME

RFC 2045 beschreibt eine Base64-Variante, die als MIME bekannt ist . Diese Variante verwendet das in Tabelle 1 von RFC 2045 dargestellte Base64-Alphabet zum Codieren und Decodieren. Der codierte Ausgabestream ist in Zeilen mit maximal 76 Zeichen organisiert. Jede Zeile (mit Ausnahme der letzten Zeile) ist über ein Zeilentrennzeichen von der nächsten Zeile getrennt. Alle Zeilentrennzeichen oder andere Zeichen, die nicht im Base64-Alphabet enthalten sind, werden beim Decodieren ignoriert.

URL und Dateiname sicher

RFC 4648 beschreibt eine Base64-Variante, die als URL und Dateiname Safe bezeichnet wird . Diese Variante verwendet das in Tabelle 2 von RFC 4648 dargestellte Base64-Alphabet zum Codieren und Decodieren. Das Alphabet ist identisch mit dem zuvor gezeigten Alphabet, außer dass es -ersetzt +und _ersetzt /. Es werden keine Zeilentrennzeichen ausgegeben. Der Decoder lehnt eine Codierung ab, die Zeichen außerhalb des Base64-Alphabets enthält.

Die Base64-Codierung ist nützlich im Zusammenhang mit langen Binärdaten und HTTP-GET-Anforderungen. Die Idee ist, diese Daten zu verschlüsseln und dann an die HTTP-GET-URL anzuhängen. Wenn die Basic- oder MIME-Variante verwendet würde, müssten alle +oder /Zeichen in den codierten Daten URL-codiert in hexadezimale Sequenzen ( +wird %2Bund /wird %2F). Die resultierende URL-Zeichenfolge wäre etwas länger. Durch das Ersetzen +durch -und /durch _URL und Dateiname Safe werden keine URL-Codierer / -Decodierer (und deren Auswirkungen auf die Länge der codierten Werte) benötigt. Diese Variante ist auch nützlich, wenn die codierten Daten für einen Dateinamen verwendet werden sollen, da Unix- und Windows-Dateinamen keine enthalten dürfen /.

Arbeiten mit der Base64-API von Java

Java 8 introduced a Base64 API consisting of the java.util.Base64 class along with its Encoder and Decoder nested static classes. Base64 presents several static methods for obtaining encoders and decoders:

  • Base64.Encoder getEncoder(): Return an encoder for the Basic variant.
  • Base64.Decoder getDecoder(): Return a decoder for the Basic variant.
  • Base64.Encoder getMimeEncoder(): Return an encoder for the MIME variant.
  • Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator): Return an encoder for a modified MIME variant with the given lineLength (rounded down to the nearest multiple of 4 -- output not separated into lines when lineLength<= 0) and lineSeparator. It throws java.lang.IllegalArgumentException when lineSeparator includes any Base64 alphabet character presented in Table 1 of RFC 2045.

    RFC 2045's encoder, which is returned from the noargument getMimeEncoder() method, is rather rigid. For example, that encoder creates encoded text with fixed line lengths (except for the last line) of 76 characters. If you want an encoder to support RFC 1421, which dicates a fixed line length of 64 characters, you need to use getMimeEncoder(int lineLength, byte[] lineSeparator).

  • Base64.Decoder getMimeDecoder(): Return a decoder for the MIME variant.
  • Base64.Encoder getUrlEncoder(): Return an encoder for the URL and Filename Safe variant.
  • Base64.Decoder getUrlDecoder(): Return a decoder for the URL and Filename Safe variant.

Base64.Encoder presents several threadsafe instance methods for encoding byte sequences. Passing the null reference to one of the following methods results in java.lang.NullPointerException:

  • byte[] encode(byte[] src): Encode all bytes in src to a newly-allocated byte array, which this method returns.
  • int encode(byte[] src, byte[] dst): Encode all bytes in src to dst (starting at offset 0). If dst isn't big enough to hold the encoding, IllegalArgumentException is thrown. Otherwise, the number of bytes written to dst is returned.
  • ByteBuffer encode(ByteBuffer buffer): Encode all remaining bytes in buffer to a newly-allocated java.nio.ByteBuffer object. Upon return, buffer's position will be updated to its limit; its limit won't have been changed. The returned output buffer's position will be zero and its limit will be the number of resulting encoded bytes.
  • String encodeToString(byte[] src): Encode all bytes in src to a string, which is returned. Invoking this method is equivalent to executing new String(encode(src), StandardCharsets.ISO_8859_1).
  • Base64.Encoder withoutPadding(): Return an encoder that encodes equivalently to this encoder, but without adding any padding character at the end of the encoded byte data.
  • OutputStream wrap(OutputStream os): Wickeln Sie einen Ausgabestream zum Codieren von Byte-Daten. Es wird empfohlen, den zurückgegebenen Ausgabestream nach der Verwendung umgehend zu schließen. Dabei werden alle möglichen verbleibenden Bytes in den zugrunde liegenden Ausgabestream übertragen. Durch Schließen des zurückgegebenen Ausgabestreams wird der zugrunde liegende Ausgabestream geschlossen.

Base64.Decoderpräsentiert verschiedene threadsichere Instanzmethoden zum Decodieren von Bytesequenzen. Das Übergeben der Nullreferenz an eine der folgenden Methoden führt zu NullPointerException: