Kodowanie i dekodowanie Base64 w Javie 8

Java 8 zostanie zapamiętana głównie z powodu wprowadzenia lambd, strumieni, nowego modelu daty / czasu oraz silnika JavaScript Nashorn do Javy. Niektórzy zapamiętają również Javę 8 do wprowadzenia różnych małych, ale przydatnych funkcji, takich jak API Base64. Co to jest Base64 i jak korzystać z tego interfejsu API? Ten post odpowiada na te pytania.

Co to jest Base64?

Base64 to schemat kodowania binarnego na tekst, który reprezentuje dane binarne w drukowalnym formacie łańcucha ASCII, tłumacząc je na reprezentację radix-64. Każda cyfra Base64 reprezentuje dokładnie 6 bitów danych binarnych.

Żądanie Base64 o dokumenty komentarza

Base64 został po raz pierwszy opisany (ale nie nazwany) w RFC 1421: Zwiększenie prywatności dla internetowej poczty elektronicznej: Część I: Procedury szyfrowania wiadomości i uwierzytelniania. Później został oficjalnie przedstawiony jako Base64 w RFC 2045: Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies, a następnie ponownie odwiedzony w RFC 4648: The Base16, Base32 i Base64 Data Encodings.

Base64 służy do zapobiegania modyfikowaniu danych podczas przesyłania przez systemy informacyjne, takie jak poczta e-mail, które mogą nie być czyste 8-bitowe (mogą zniekształcać wartości 8-bitowe). Na przykład dołączasz obraz do wiadomości e-mail i chcesz, aby obraz dotarł na drugi koniec bez zniekształcenia. Twoje oprogramowanie pocztowe Base64 koduje obraz i wstawia równoważny tekst do wiadomości, jak pokazano poniżej:

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

Ilustracja pokazuje, że ten zakodowany obraz zaczyna się /i kończy na =. ...Wskazuje tekst, że nie wykazano dla zwięzłości. Zwróć uwagę, że całe kodowanie dla tego lub dowolnego innego przykładu jest około 33 procent większe niż oryginalne dane binarne.

Oprogramowanie poczty e-mail adresata dekoduje kodowany obraz tekstowy w standardzie Base64, aby przywrócić oryginalny obraz binarny. W tym przykładzie obraz byłby wyświetlany w tekście z pozostałą częścią wiadomości.

Kodowanie i dekodowanie Base64

Base64 opiera się na prostych algorytmach kodowania i dekodowania. Pracują z 65-znakowym podzbiorem US-ASCII, w którym każdy z pierwszych 64 znaków jest odwzorowywany na równoważną 6-bitową sekwencję binarną. Oto alfabet:

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

65. znak ( =) służy do dopełniania tekstu zakodowanego algorytmem Base64 do całkowitego rozmiaru, jak wyjaśniono w skrócie.

Właściwość podzbioru

Ten podzbiór ma ważną właściwość, że jest reprezentowany identycznie we wszystkich wersjach ISO 646, w tym US-ASCII, a wszystkie znaki w podzbiorze są również reprezentowane identycznie we wszystkich wersjach EBCDIC.

Algorytm kodowania otrzymuje strumień wejściowy 8-bitowych bajtów. Przypuszcza się, że ten strumień jest uporządkowany z najbardziej znaczącym bitem jako pierwszym: pierwszy bit to najstarszy bit w pierwszym bajcie, ósmy to najmniej znaczący bit w tym bajcie i tak dalej.

Od lewej do prawej te bajty są zorganizowane w 24-bitowe grupy. Każda grupa jest traktowana jako cztery połączone grupy 6-bitowe. Każda 6-bitowa grupa indeksuje do tablicy 64 drukowalnych znaków; wynikowy znak jest wyprowadzany.

Gdy na końcu kodowanych danych dostępnych jest mniej niż 24 bity, dodawane są bity zerowe (po prawej stronie), aby utworzyć całkowitą liczbę 6-bitowych grup. Następnie =może zostać wyprowadzony jeden lub dwa znaki wypełniające. Należy wziąć pod uwagę dwa przypadki:

  • Pozostały jeden bajt: cztery bity zerowe są dołączane do tego bajtu, tworząc dwie 6-bitowe grupy. Każda grupa indeksuje tablicę i wynikowy znak jest wyprowadzany. Po tych dwóch znakach =wyprowadzane są dwa znaki wypełniające.
  • Dwa pozostałe bajty: dwa bity zerowe są dołączane do drugiego bajtu, tworząc trzy 6-bitowe grupy. Każda grupa indeksuje tablicę i wynikowy znak jest wyprowadzany. Po tych trzech znakach =wyprowadzany jest jeden znak wypełniacza.

Rozważmy trzy przykłady, aby dowiedzieć się, jak działa algorytm kodowania. Najpierw załóżmy, że chcemy zakodować @!*:

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

Będziemy kontynuować, skracając sekwencję wejściową do @!:

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=

Ostatni przykład skraca sekwencję wprowadzania do @:

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==

Algorytm dekodowania jest odwrotnością algorytmu kodowania. Jednak po wykryciu znaku spoza alfabetu Base64 lub nieprawidłowej liczby znaków padów można podjąć odpowiednie działania.

Warianty Base64

Opracowano kilka wariantów Base64. Niektóre warianty wymagają, aby zakodowany strumień wyjściowy był podzielony na wiele wierszy o ustalonej długości, przy czym każdy wiersz nie przekraczał określonego limitu długości i (z wyjątkiem ostatniego wiersza) był oddzielony od następnego wiersza za pomocą separatora wiersza (powrót karetki, \rpo którym następuje wysunięcie wiersza \n). Opisuję trzy warianty obsługiwane przez API Base64 w Javie 8. Sprawdź wpis Wikipedii dotyczący Base64, aby uzyskać pełną listę wariantów.

Podstawowy

RFC 4648 opisuje wariant Base64 znany jako Basic . Ten wariant wykorzystuje do kodowania i dekodowania alfabet Base64 przedstawiony w tabeli 1 dokumentów RFC 4648 i RFC 2045 (i pokazany wcześniej w tym poście). Koder traktuje zakodowany strumień wyjściowy jako jedną linię; nie są wyświetlane żadne separatory linii. Dekoder odrzuca kodowanie, które zawiera znaki spoza alfabetu Base64. Zwróć uwagę, że te i inne zastrzeżenia można pominąć.

MIM

RFC 2045 opisuje wariant Base64 znany jako MIME . Ten wariant wykorzystuje do kodowania i dekodowania alfabet Base64 przedstawiony w tabeli 1 dokumentu RFC 2045. Zakodowany strumień wyjściowy jest podzielony na linie nie dłuższe niż 76 znaków; każda linia (z wyjątkiem ostatniej) jest oddzielona od następnej linii separatorem linii. Wszystkie separatory wierszy lub inne znaki, których nie ma w alfabecie Base64, są ignorowane podczas dekodowania.

Adres URL i nazwa pliku są bezpieczne

RFC 4648 opisuje wariant Base64 znany jako URL i bezpieczna nazwa pliku . Ten wariant wykorzystuje do kodowania i dekodowania alfabet Base64 przedstawiony w tabeli 2 dokumentu RFC 4648. Alfabet jest identyczny z alfabetem pokazanym wcześniej, z wyjątkiem tego, że -zastępuje +i _zastępuje /. Nie są wyświetlane żadne separatory linii. Dekoder odrzuca kodowanie, które zawiera znaki spoza alfabetu Base64.

Kodowanie Base64 jest przydatne w kontekście długich danych binarnych i żądań HTTP GET. Chodzi o to, aby zakodować te dane, a następnie dołączyć je do adresu URL HTTP GET. Gdyby użyto wariantu Basic lub MIME, wszystkie znaki +lub /w zakodowanych danych musiałyby być zakodowane w postaci adresów URL w sekwencjach szesnastkowych ( +staje się %2Bi /staje się %2F). Wynikowy ciąg adresu URL byłby nieco dłuższy. Zastępując +z -i /z _, URL i nazwę pliku Safe eliminuje potrzebę koderów URL / dekodery (i ich wpływ na długości zakodowanych wartości). Ten wariant jest również przydatny, gdy zakodowane dane mają być użyte jako nazwa pliku, ponieważ nazwy plików w systemach Unix i Windows nie mogą zawierać /.

Praca z API Base64 w Javie

Java 8 wprowadził API Base64 składający się z java.util.Base64klasy wraz ze swoimi Encoderand Decoderzagnieżdżonych staticklas. Base64przedstawia kilka staticmetod uzyskiwania koderów i dekoderów:

  • Base64.Encoder getEncoder(): Zwróć koder dla wariantu podstawowego.
  • Base64.Decoder getDecoder(): Zwróć dekoder dla wariantu podstawowego.
  • Base64.Encoder getMimeEncoder(): Zwraca koder dla wariantu MIME.
  • Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator): Zwraca koder dla zmodyfikowanego wariantu MIME z podanym lineLength(zaokrąglonym w dół do najbliższej wielokrotności 4 - wyjście nie jest rozdzielone na linie, gdy lineLength<= 0) i lineSeparator. Jest generowany, java.lang.IllegalArgumentExceptiongdy lineSeparatorzawiera dowolny znak alfabetu Base64 przedstawiony w tabeli 1 dokumentu RFC 2045.

    Koder RFC 2045, który jest zwracany z getMimeEncoder()metody noargument , jest raczej sztywny. Na przykład ten koder tworzy zakodowany tekst ze stałą długością linii (z wyjątkiem ostatniej linii) wynoszącą 76 znaków. Jeśli chcesz, aby koder obsługiwał RFC 1421, który określa stałą długość linii 64 znaków, musisz użyć getMimeEncoder(int lineLength, byte[] lineSeparator).

  • Base64.Decoder getMimeDecoder(): Zwróć dekoder dla wariantu MIME.
  • Base64.Encoder getUrlEncoder(): Zwróć koder dla wariantu bezpiecznego adresu URL i nazwy pliku.
  • Base64.Decoder getUrlDecoder(): Zwróć dekoder dla wersji URL i bezpiecznej nazwy pliku.

Base64.Encoderprzedstawia kilka metod instancji z ochroną wątków do kodowania sekwencji bajtów. Przekazanie zerowego odwołania do jednej z następujących metod powoduje java.lang.NullPointerException:

  • byte[] encode(byte[] src): Zakoduj wszystkie bajty w srcnowo przydzielonej tablicy bajtów, którą ta metoda zwraca.
  • 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): Opakowanie strumienia wyjściowego do kodowania danych bajtowych. Zaleca się natychmiastowe zamknięcie zwróconego strumienia wyjściowego po użyciu, podczas którego opróżni on wszystkie możliwe pozostałe bajty do bazowego strumienia wyjściowego. Zamknięcie zwróconego strumienia wyjściowego spowoduje zamknięcie podstawowego strumienia wyjściowego.

Base64.Decoderprzedstawia kilka metod instancji z ochroną wątków do dekodowania sekwencji bajtów. Przekazanie zerowego odwołania do jednej z następujących metod powoduje NullPointerException: