Dlaczego kodowanie base64 wymaga dopełnienia, jeśli długość wejściowa nie jest podzielna przez 3?


102

Jaki jest cel dopełnienia w kodowaniu base64. Poniżej znajduje się wyciąg z Wikipedii:

„Przydzielany jest dodatkowy znak wypełniający, którego można użyć do wymuszenia na zakodowanym wyjściu liczby całkowitej będącej wielokrotnością 4 znaków (lub równoważnie, gdy niezakodowany tekst binarny nie jest wielokrotnością 3 bajtów); te znaki wypełniające należy następnie odrzucić podczas dekodowania, ale nadal pozwalają na obliczenie efektywnej długości niezakodowanego tekstu, gdy jego wejściowa długość binarna nie byłaby wielokrotnością 3 bajtów (ostatni znak niebędący wypełnieniem jest normalnie kodowany tak, że ostatni 6-bitowy blok, który reprezentuje, będzie zerowy - dopełniony na najmniej znaczących bitach, na końcu zakodowanego strumienia mogą wystąpić co najwyżej dwa znaki wypełnienia). "

Napisałem program, który mógł kodować base64 dowolny ciąg i dekodować dowolny ciąg zakodowany w base64. Jaki problem rozwiązuje wypełnienie?

Odpowiedzi:


214

Twój wniosek, że wypełnienie jest niepotrzebne, jest słuszny. Długość wejścia zawsze można jednoznacznie określić na podstawie długości zakodowanej sekwencji.

Jednak dopełnianie jest przydatne w sytuacjach, gdy łańcuchy kodowane algorytmem base64 są łączone w taki sposób, że tracone są długości poszczególnych sekwencji, co może się zdarzyć na przykład w bardzo prostym protokole sieciowym.

Jeśli ciągi nieparzyste są łączone, niemożliwe jest odzyskanie oryginalnych danych, ponieważ informacja o liczbie nieparzystych bajtów na końcu każdej pojedynczej sekwencji zostaje utracona. Jeśli jednak używane są sekwencje wypełnione, nie ma niejednoznaczności, a sekwencja jako całość może zostać poprawnie zdekodowana.

Edycja: ilustracja

Załóżmy, że mamy program, który koduje słowa base64, łączy je i wysyła przez sieć. Koduje „I”, „AM” i „TJM”, umieszcza wyniki razem bez wypełniania i przesyła je.

  • Ikoduje do SQ( SQ==z dopełnieniem)
  • AMkoduje do QU0( QU0=z dopełnieniem)
  • TJMkoduje do VEpN( VEpNz dopełnieniem)

Więc przesyłane dane są SQQU0VEpN. Odbiornik base64 dekoduje to zgodnie I\x04\x14\xd1Q)z przeznaczeniem IAMTJM. Wynik jest bezsensowny, ponieważ nadawca zniszczył informacje o tym, gdzie kończy się każde słowo w zakodowanej sekwencji. Gdyby nadawca wysłał SQ==QU0=VEpNzamiast tego, odbiorca mógłby zdekodować to jako trzy oddzielne sekwencje base64, które połączyłyby się, aby dać IAMTJM.

Po co zawracać sobie głowę wyściółką?

Dlaczego nie zaprojektować protokołu tak, aby poprzedzał każde słowo liczbą całkowitą? Wtedy odbiornik mógłby poprawnie dekodować strumień i nie byłoby potrzeby wypełniania.

To świetny pomysł, o ile znamy długość danych, które kodujemy, zanim zaczniemy je kodować. Ale co by było, gdybyśmy zamiast słów zakodowali fragmenty wideo z kamery na żywo? Możemy nie znać z góry długości każdego fragmentu.

Gdyby protokół używał wypełnienia, nie byłoby w ogóle potrzeby przesyłania długości. Dane mogą być zakodowane, gdy nadchodzą z kamery, każdy fragment zostałby zakończony wypełnieniem, a odbiornik byłby w stanie poprawnie zdekodować strumień.

Oczywiście jest to bardzo wymyślony przykład, ale być może ilustruje, dlaczego wypełnienie mogłoby być pomocne w niektórych sytuacjach.


24
+1 Jedyna odpowiedź, która faktycznie dostarcza rozsądnej odpowiedzi poza „ponieważ lubimy gadatliwość i nadmiarowość z jakiegoś niewytłumaczalnego powodu”.
Nieprawidłowy

1
Działa to dobrze w przypadku fragmentów, które są wyraźnie zakodowane, ale po zdekodowaniu oczekuje się, że zostaną niepodzielnie połączone. Jeśli wyślesz U0FNSQ == QU0 =, możesz zrekonstruować zdanie, ale stracisz słowa, które składają się na to zdanie. Chyba lepiej niż nic. Warto zauważyć, że program GNU base64 automatycznie obsługuje połączone kodowania.
Marcelo Cantos

2
A co, jeśli długość słów byłaby wielokrotnością 3? Ten głupi sposób konkatenacji niszczy informacje (końcówki słów), a nie usuwanie wypełnienia.
GreenScape

2
Łączenie Base64 umożliwia koderom równoległe przetwarzanie dużych fragmentów bez konieczności wyrównywania rozmiarów fragmentów do wielokrotności trzech. Podobnie, jako szczegół implementacji, może istnieć koder, który musi opróżnić wewnętrzny bufor danych o rozmiarze, który nie jest wielokrotnością trzech.
Andre D,

2
Ta odpowiedź może sprawić, że pomyślisz, że możesz zdekodować coś takiego jak „SQ == QU0 = VEpN”, po prostu przekazując to dekoderowi. Właściwie wydaje się, że nie możesz, na przykład implementacje w javascript i php nie obsługują tego. Rozpoczynając od połączonego ciągu, musisz albo dekodować 4 bajty na raz, albo podzielić ciąg po dopełnieniu znaków. Wygląda na to, że te implementacje po prostu ignorują znaki wypełniające, nawet jeśli są w środku łańcucha.
Roman

39

A propos, oto podstawowy konwerter do dowolnej konwersji bazowej, który dla Ciebie stworzyłem. Cieszyć się! https://convert.zamicol.com/

Co to są znaki dopełniające?

Znaki dopełniające pomagają spełnić wymagania dotyczące długości i nie mają znaczenia.

Dziesiętny przykład dopełnienia: biorąc pod uwagę dowolne wymaganie, aby wszystkie ciągi miały długość 8 znaków, liczba 640 może spełnić to wymaganie, używając poprzedzających 0 jako znaków dopełniających, ponieważ nie mają one znaczenia „00000640”.

Kodowanie binarne

Paradygmat bajtów: bajt jest de facto standardową jednostką miary, a każdy schemat kodowania musi odnosić się z powrotem do bajtów.

Base256 dokładnie pasuje do tego paradygmatu. Jeden bajt to jeden znak w base256.

Base16 , szesnastkowy lub szesnastkowy, wykorzystuje 4 bity na każdy znak. Jeden bajt może reprezentować dwa znaki base16.

Base64 nie pasuje równomiernie do paradygmatu bajtów (ani base32), w przeciwieństwie do base256 i base16. Wszystkie znaki base64 mogą być reprezentowane w 6 bitach, 2 bity krótkie od pełnego bajtu.

Możemy przedstawić kodowanie base64 w porównaniu z paradygmatem bajtów jako ułamek: 6 bitów na znak w 8 bitach na bajt . Zmniejszono ten ułamek o 3 bajty ponad 4 znaki.

Ten współczynnik, 3 bajty na każde 4 znaki base64, jest regułą, której chcemy przestrzegać podczas kodowania base64. Kodowanie Base64 może obiecać tylko pomiary z pakietami 3-bajtowymi, w przeciwieństwie do base16 i base256, gdzie każdy bajt może stać sam.

Dlaczego więc zachęca się do wypełniania, mimo że kodowanie mogłoby działać dobrze bez znaków wypełniających?

Jeśli długość strumienia jest nieznana lub jeśli może być pomocne dokładne poznanie końca strumienia danych, użyj dopełnienia. Znaki wypełniające wyraźnie informują, że te dodatkowe miejsca powinny być puste, i wyklucza jakąkolwiek dwuznaczność. Nawet jeśli długość jest nieznana z dopełnieniem, będziesz wiedzieć, gdzie kończy się strumień danych.

Jako kontrprzykład, niektóre standardy, takie jak JOSE , nie pozwalają na dopełnianie znaków. W takim przypadku, jeśli czegoś brakuje, podpis kryptograficzny nie będzie działał lub będzie brakować innych znaków spoza base64 (np. „.”). Chociaż założenia dotyczące długości nie są poczynione, wypełnienie nie jest potrzebne, ponieważ jeśli coś jest nie tak, po prostu nie zadziała.

Dokładnie tak mówi dokument RFC dotyczący base64 :

W niektórych przypadkach użycie dopełnienia („=”) w danych zakodowanych w oparciu o zasady nie jest wymagane ani używane. W ogólnym przypadku, gdy nie można przyjąć założeń dotyczących rozmiaru transportowanych danych, wymagane jest wypełnienie, aby uzyskać prawidłowe zdekodowane dane.

[…]

Krok dopełniania w bazie 64, jeśli [...] jest nieprawidłowo zaimplementowany, prowadzi do nieznaczących zmian w zakodowanych danych. Na przykład, jeśli dane wejściowe to tylko jeden oktet dla podstawowego kodowania 64, wówczas używane są wszystkie sześć bitów pierwszego symbolu, ale używane są tylko pierwsze dwa bity następnego symbolu. Te bity wypełnienia MUSZĄ być ustawione na zero przez zgodne kodery, co jest opisane w opisach wypełnienia poniżej. Jeśli ta właściwość nie jest zachowana, nie ma kanonicznej reprezentacji danych zakodowanych w oparciu o zasady, a wiele ciągów zakodowanych w oparciu o zasady może być zdekodowanych na te same dane binarne. Jeśli ta właściwość (i inne omówione w tym dokumencie) jest zachowana, gwarantowane jest kodowanie kanoniczne.

Dopełnienie pozwala nam dekodować kodowanie base64 z obietnicą braku utraconych bitów. Bez wypełnienia nie ma już wyraźnego potwierdzenia pomiaru w pakietach trzech bajtów. Bez dopełnienia możesz nie być w stanie zagwarantować dokładnego odtworzenia oryginalnego kodowania bez dodatkowych informacji, zwykle z innego miejsca na stosie, takich jak TCP, sumy kontrolne lub inne metody.

Przykłady

Oto przykład formularza RFC 4648 ( http://tools.ietf.org/html/rfc4648#section-8 )

Każdy znak wewnątrz funkcji „BASE64” zajmuje jeden bajt (base256). Następnie tłumaczymy to na 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.)

Oto koder, z którym możesz się pobawić: http://www.motobit.com/util/base64-decoder-encoder.asp


16
-1 To fajny i dokładny post o działaniu systemów liczbowych, ale nie wyjaśnia, dlaczego stosuje się dopełnienie, skoro kodowanie działałoby idealnie bez.
Matti Virkkunen

2
Czy w ogóle przeczytałeś pytanie? Nie potrzebujesz dopełnienia do prawidłowego dekodowania.
Navin

3
Myślę, że ta odpowiedź faktycznie wyjaśniła powód podany tutaj: „nie możemy już zagwarantować dokładnego odtworzenia oryginalnego kodowania bez dodatkowych informacji”. To naprawdę proste, dopełnienie informuje nas, że otrzymaliśmy pełne kodowanie. Za każdym razem, gdy masz 3 bajty, możesz bezpiecznie założyć, że możesz to zrobić i odszyfrować, nie martw się, buczenie ... może przyjdzie jeszcze jeden bajt, prawdopodobnie zmieniając kodowanie.
Didier A.

@DidierA. Skąd wiesz, że w podciągu base64 nie ma więcej 3 bajtów? Aby zdekodować a char*, potrzebujesz rozmiaru łańcucha lub terminatora null. Wypełnienie jest zbędne. Stąd pytanie OP.
Navin

4
@ Navin Jeśli dekodujesz strumieniowo bajty base64, nie znasz długości, z dopełnieniem 3 bajtów wiesz, że za każdym razem, gdy masz 3 bajty, możesz przetwarzać 4 znaki, aż do końca strumienia. Bez tego może być konieczne cofnięcie się, ponieważ następny bajt może spowodować zmianę poprzedniego znaku, dzięki czemu można mieć pewność, że poprawnie go zdekodowałeś, gdy osiągniesz koniec strumienia. Nie jest to więc zbyt przydatne, ale ma kilka skrajnych przypadków, w których możesz go chcieć.
Didier A.

2

W dzisiejszych czasach nie ma z tego wiele korzyści. Spójrzmy więc na to jako na pytanie, jaki mógł być pierwotny cel historyczny.

Kodowanie Base64 pojawia się po raz pierwszy w dokumencie RFC 1421 z 1993 r. Ten dokument RFC w rzeczywistości koncentruje się na szyfrowaniu wiadomości e-mail, a base64 jest opisany w jednej małej sekcji 4.3.2.4 .

W tym dokumencie RFC nie wyjaśniono celu wypełnienia. Najbliższą nam wzmianką o pierwotnym celu jest to zdanie:

Pełne kodowanie jest zawsze zakończone na końcu wiadomości.

Nie sugeruje konkatenacji (najlepsza odpowiedź tutaj) ani łatwości implementacji jako wyraźnego celu wypełnienia. Jednak biorąc pod uwagę cały opis, nie jest nierozsądne założenie, że mogło to mieć na celu ułatwienie dekoderowi odczytywania danych wejściowych w jednostkach 32-bitowych ( „kwantach” ). Obecnie nie przynosi to żadnych korzyści, jednak w 1993 roku niebezpieczny kod C najprawdopodobniej faktycznie wykorzystałby tę właściwość.


1
W przypadku braku wypełnienia, próba połączenia dwóch ciągów, gdy długość pierwszego łańcucha nie jest wielokrotnością trzech, często dawałaby pozornie prawidłowy ciąg, ale zawartość drugiego łańcucha byłaby nieprawidłowo zdekodowana. Dodanie wypełnienia gwarantuje, że tak się nie stanie.
supercat

1
@supercat Gdyby taki był cel, czy nie byłoby łatwiej zakończyć każdy ciąg znaków base64 pojedynczym „=”? Średnia długość byłaby krótsza i nadal zapobiegałaby błędnym konkatenacjom.
Roman Starkov

2
Średnia długość b'Zm9vYmFyZm9vYg==' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy' b'Zm9vYmFyZm9vYmFyZg==' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v' jest taka sama jak w przypadku b'Zm9vYmFyZm9vYg=' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy=' b'Zm9vYmFyZm9vYmFyZg=' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v='
Scott
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.