Kategorie
Publicystyka Zrób to sam

Wysyłamy plik pocztówką czyli do czego służy kodowanie Base64

Załóżmy, że wysyłamy komuś pocztówkę z wakacji i koniecznie chcemy zamieścić w treści pozdrowień… plik komputerowy. Nie jest to szczególnie częste, ale skoro ludzie tworzą mieszczące się na wizytówce gry i programy generujące grafikę 3D to kartka pocztowa jako nośnik plików nie może być AŻ TAK egzotyczna.

W tym tekście zastanowimy się, w jaki sposób zapisać treść pliku przy użyciu jak najmniejszej liczby znaków. Dzięki temu będziemy mieć satysfakcję, że nie narobiliśmy się niepotrzebnie a na pocztówce zmieszczą się możliwie duże pliki. Na końcu artykułu będzie miał miejsce nagły zwrot akcji, w którym okaże się, że wcale nie chodziło nam o pocztówki.

Krótkie przypomnienie – bit w informatyce to jedynka lub zero. Symbolizuje on pojedynczą cyfrę w dwójkowym systemie liczbowym (nie musisz rozumieć, na czym on polega – wiedza ta nie jest potrzebna do lektury). Osiem bitów tworzy bajt. Każdy z ośmiu bitów w bajcie może być jedynką lub zerem, co daje nam 256 kombinacji. Kilka pierwszych i ostatnich to:

00000000
00000001
00000010
00000011
00000100
00000101
00000110
00000111
00001000
[…]
11111101
11111110
11111111

Skoro istnieje dokładnie 256 kombinacji ośmiu bitów, to tyle różnych wartości może przyjąć jeden bajt. Jeśli wraz z odbiorcą umówimy się na zestaw 256 różnych symboli oznaczających kolejne wartości, to wysyłanie pliku pocztówką przestaje być problemem – jednemu bajtowi będzie odpowiadał jeden znaczek a odbiorca bez trudu zrekonstruuje plik, który zapisaliśmy. Zazwyczaj jednak chcielibyśmy skorzystać z symboli które na pewno znamy, jak literki i cyferki. I tu robi się problem, bo jest ich mniej niż 256.

Jak pisałem w artykule o emotikonkach, od ponad 50 lat w użyciu jest standard ASCII czyli ogólnoświatowa umowa dotycząca numerowania literek, cyferek i innych znaków możliwych do wpisania z klawiatury. Dzięki temu wszystkie współczesne komputery wiedzą, że literka “A” występuje pod numerem 65 (binarnie 01000001) zaś literka “m” to numer 109 (binarnie 01101101). Tablicę kodów ASCII możemy znaleźć w Wikipedii, poniżej te dwie literki zostały wyróżnione:

kolumna BIN to numer zapisany dwójkowo a DEC to numer zapisany dziesiętnie, HEX będzie objaśniony za chwilę. Kolumna ZNAK pokazuje literkę o danym numerze

Widzimy, że zapisanie pliku na pocztówce znakami ASCII nam się nie uda, bo pierwsze 32 znaki nie mają przypisanych żadnych literek, tylko stanowią tzw. kody sterujące. Wywodzą się one z czasów, gdy po świecie chodziły dinozaury zaś wiadomości przesyłano przy użyciu dalekopisów. Kody sterujące obecne w standardzie ASCII były wykorzystywane na przykład do tego, by zlecić drukarce wysunięcie papieru o jeden wiersz do góry (kod o numerze 10), cofnięcie głowicy drukującej do początku wiersza (kod 13) albo dryndnięcie dzwonkiem w celu zwrócenia uwagi operatora (kod 7).

Brakuje nam znaków symbolizujących bajty o wartościach od 0 do 31, a nie doszliśmy jeszcze do numerów 128-255, których ASCII nie uwzględnia w ogóle. Jeśli więc chcemy zapisać na pocztówce zestaw wszystkich możliwych bajtów przy użyciu literek i cyferek z klawiatury, na jeden bajt musi przypadać więcej niż jeden znak.

Co zapiszemy na pocztówce

Umówmy się, że chcemy wysłać dwie pocztówki. Na pierwszej zapiszemy wszystkie 256 możliwych bajtów, o wartościach od 0 do 255 (to nasz główny eksperyment). Jeśli chcesz powtarzać moje eksperymenty, plik źródłowy znajdziesz tutaj: BAJTY_0_255.BIN.

Na drugiej pocztówce chcemy zapisać 400 bajtów kodujących następujący obrazek o rozmiarach 20×20 pikseli:

Pojedynczy bajt będzie niósł informację o jasności każdego piksela, startując od zera (kolor czarny), przez kolejne odcienie szarości aż do 255 (kolor biały). Jeśli zapiszemy te wartości w tabelce 20×20 komórek, będzie ona wyglądała następująco:

Ciekawostka – gdy włączymy w Excelu formatowanie warunkowe, ujrzymy zarysy naszego pierwotnego obrazka:

Plik z bajtami składającymi się na powyższy obrazek możesz znaleźć tutaj: OBRAZEK_400.BIN. Uwaga! Nie otworzysz go w żadnym programie graficznym, to po prostu 400 bajtów o wartościach 255, 255, 255, 255, 150, 0, 71, 84 i tak dalej. Do tego pliku wrócimy w zaskakującym finale na samym końcu tekstu.

Metoda pierwsza – zapis dziesiętny

Ta metoda zapisu pliku na pocztówce jest dość oczywista – każdy bajt zapiszemy w postaci liczby dziesiętnej o wartości od 0 do 255. Początek i koniec zawartości pierwszej pocztówki będzie więc wyglądać następująco:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 […] 250 251 252 253 254 255

Początek i koniec zawartości drugiej pocztówki to
255 255 255 255 150 0 71 84 0 0 108 […] 0 67 137 241 255 255 187

Zauważmy, że marnujemy w ten sposób sporo miejsca – większość liczb z zakresu 0-255 składa się z trzech cyfr, do tego potrzebujemy spacji jako separatora kolejnych liczb. Średnio na jeden bajt zużyjemy w tym sposobie zapisu ponad trzy i pół znaku.

Niewielkim ulepszeniem będzie rezygnacja ze spacji i zapisywanie wszystkich liczb trzema cyframi. Pierwsza pocztówka będzie wówczas wyglądać mniej więcej tak:
000001002003004005006007008009010011012013014[…]250251252253254255

Oszczędność miejsca jest jednak niewielka – zeszliśmy do 3 znaków na jeden bajt. Możemy to zrobić lepiej!

Metoda druga – zapis szesnastkowy

Tym razem użyjemy zapisu szesnastkowego czyli takiego, w którym pojedyncza cyfra reprezentuje wartość od zera do piętnastu – w stosowanej powszechnie konwencji kolejne cyfry to: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Nie musisz rozumieć, jak dokładnie działają systemy liczbowe o różnych podstawach, pozostawmy to osobom pasjonującym się komputerami. To, co ważne, zauważymy w tablicy kodów ASCII:

Kolumna HEX pokazuje numerację w notacji szesnastkowej (heksadecymalnej). Widzimy, że w tym systemie liczba składa się z dwóch cyferek nawet tam, gdzie zapis dziesiętny wymaga trzech cyfr. Podświetlony przykład: dziesiętne 109 to szesnastkowe 6D. Gdy standardowy Kalkulator z systemu Windows przełączymy w tryb programisty, przekonamy się, że dziesiętna wartość 255 to szesnastkowe FF, co oznacza, że wszystkie liczby z zakresu 0-255 możemy zapisać szesnastkowo na dwóch znakach.

Również w tym przypadku możemy wszystkie wartości zapisywać bez spacji, czyli:
000102030405060708090a0b0c[…]f9fafbfcfdfeff

Nasze pocztówki wymagają teraz użycia dwóch znaków na każdy bajt. Jest znacznie lepiej, ale to nie jest nasze ostatnie słowo.

CyberChef na ratunek!

Ręczne operacje na różnych systemach liczbowych są żmudne i podatne na błędy – szczególnie, gdy na chwilę stracimy koncentrację i zapomnimy, czy napis “100” to binarny zapis liczby cztery, dziesiętny zapis setki czy heksadecymalny zapis wartości dwieście pięćdziesiąt sześć.

Podczas przygotowania tego tekstu korzystałem z narzędzia CyberChef dostępnego na stronie gchq.github.io/CyberChef/. Uwaga – to prawdziwe narzędzie dla cyberszpiegów! Projekt zainicjowała i prowadzi brytyjska Centrala Łączności Rządowej znana dawniej pod nazwą Rządowej Szkoły Kodów i Szyfrów. Czadersko, nie?

CyberChef to kombajn do szybkiego prototypowania systemów przetwarzających i przekształcających dane w najróżniejszy sposób, ze szczególnych uwzględnieniem (de)kodowania, (de)szyfrowania oraz filtrowania. W tym artykule jedynie rzucimy okiem na najbardziej podstawowe operacje.

Interfejs CyberChefa wygląda następująco:

Jako dane wejściowe możemy użyć pliku. Przypomnijmy sobie nasz przykładowy plik BAJTY_0_255.BIN. Gdy otworzymy go w edytorze heksadecymalnym HxD, zobaczymy następującą zawartość:

Możemy przekonać się, że plik ten zawiera 256 bajtów o wartościach od 0 do 256. Użyjemy tego pliku do wyprodukowania ciągu znaków z tymi wartościami, ale w postaci szesnastkowej:

Dane wejściowe to tym razem nie napis lecz plik BAJTY_0_255.BIN. “Przepis” to pojedyncza komenda przekształcenia danych do postaci szesnastkowej (“To Hex”), bez żadnego oddzielania liczb od siebie (Delimiter: None).

Dane wyjściowe (“Output”) możemy teraz skopiować do schowka albo zapisać do pliku.

Metoda trzecia – zapis w formacie Base64

Wracamy do tematu, poszukamy teraz sposobu na jeszcze większe upakowanie bajtów w znakach zapisywanych na pocztówce. Krótkie przypomnienie:

  • nie mamy 256 różnych znaków, więc nie możemy zapisać jednego bajtu (ośmiu bitów) jednym znakiem
  • jeśli użyjemy szesnastu heksadecymalnych cyferek (od 0 do 9 i od A do F), każda taka cyferka mieści pół bajtu (cztery bity) a para cyferek – cały bajt (osiem bitów)

Hmmm, czy w grę wchodzi jakaś wartość pośrednia? Czy jesteśmy w stanie zakodować w jednym znaczku mniej niż osiem ale więcej niż cztery bity? Tak! Jednym znaczkiem zakodujemy sześć bitów – potrzebujemy więc do tego 64 różne symbole, bo na tyle sposobów można ustawić sześć zer i jedynek.

Jakich znaków użyjemy? Weźmy duże i małe literki alfabetu łacińskiego, 2 x 26 znaków. Dołóżmy do tego cyferki od 0 do 9. Razem 62, brakuje jeszcze dwóch. Zgodnie z dokumentem standaryzacyjnym RFC 4648 dwa brakujące znaki to “+” oraz “/”. Razem znaki te tworzą zestaw 64 “cyferek” dla systemu liczbowego o podstawie 64, po angielsku: “base 64” – stąd właśnie nazwa tego kodowania.

Co tak naprawdę udało nam się osiągnąć? Każde 3 bajty wejściowe (3×8 = 24 bity) przedstawimy jako 4 symbole w kodowaniu Base64 (4×6 = 24 bity). Narzut wynosi jedyne 33%, co jest już wartością akceptowalną.

Każdy “X” na ilustracji poniżej to jeden bit czyli jedynka lub zero.

Sześciobitowe wartości liczbowe przekładamy na znaki kodowania Base64 zgodnie z poniższą tabelą:

Jeśli liczba bajtów wejściowych nie jest podzielna przez trzy (na końcu “brakuje” jednego lub dwóch bajtów do zakodowania), to w miejsce jednego lub dwóch ostatnich znaków Base64 wstawiamy znak równości. Widać to na obrazku poniżej:

Od teraz umiemy przesyłać pliki komputerowe dalekopisem! Haha, mam was. Usługi teleksowe nie są w Polsce świadczone od lutego 2007.

Dwa ostatnie znaki kodujące Base64, plusik i ukośnik, sprawiają czasem problemy, bo na przykład w adresach URL niosą szczególne znaczenie. Z tego powodu w użyciu są też alternatywne zestawy znaków pozwalające wstawić zakodowany tekst do URL-a, użyć jako nazwy pliku, umieścić w pliku XML i tak dalej. Oto ściągawka z CyberChefa:

ZASKAKUJĄCY ZWROT AKCJI!

Tak naprawdę nie chodziło nam o pocztówki! Zamiast tego dowiedzieliśmy się, w jaki sposób kodowane są pliki załączane do e-maili!

Standard Base64 jest stosowany w znakomitej większości poczty elektronicznej przesyłanej w internecie. E-mail jest usługą starą, do dziś bazuje na protokole SMTP którego pierwsze wersje powstawały w roku 1982. Zdecydowano wówczas, że protokół będzie korzystał jedynie z podstawowego zestawu znaków ASCII (z przedziału 0-127) więc binarne załączniki trzeba było jakoś kodować. Dawniej używano np. uuencode lub BinHex, ale próbę czasu lepiej przetrwał protokół Base64.

Przekonamy się o tym w najprostszy możliwy sposób. Wróćmy do drugiego z naszych eksperymentalnych plików – OBRAZEK_400.BIN. Wstawiamy go do CyberChefa i kodujemy jako Base64.

Teraz wysyłamy sobie ten sam plik e-mailem a potem otwieramy źródło wiadomości (w przypadku Gmaila będzie to opcja “Pobierz wiadomość”, otrzymamy plik w formacie EML).

Plik ten możemy otworzyć w Notatniku

Widzimy, że załącznik w e-mailu składa się z dokładnie takiej samej sekwencji znaków, jak pole wyjściowe eksperymentu w CyberChefie (znaki nowego wiersza w innych miejscach nie mają tu znaczenia, liczą się tylko symbole “rozumiane” przez Base64).

Jakie jeszcze zastosowania ma Base64? Bywa używany do umieszczania (raczej niewielkich) plików w klasycznych, relacyjnych bazach danych. Czasem przydaje się do transportu złożonych formatów danych przez kanał pozwalający jedynie na przesyłanie tekstu. Nigdy i pod żadnym pozorem nie powinien być za to używany do kodowania haseł użytkownika – tu odpowiednie będą jedynie nowoczesne jednokierunkowe funkcje skrótu, które nie pozwalają na odtworzenie pierwotnego brzmienia hasła.

Dlaczego w ogóle powstała ta notka?

W planach mam artykuł o podpisach elektronicznych i tam również wystąpi kodowanie Base64. Zdałem sobie sprawę, że temat jest zbyt obszerny, aby zmieścił się w sensownej długości przypisie, więc potrzebna była osobna blogonotka. Mam nadzieję, że sama z siebie też jest ciekawa.

Aha, Base64 to oczywiście nie jedyny wariant kodowania o dziwnym systemie liczbowym. Poeksperymentujcie z CyberChefem – uwzględnia on także Base58, Base62 czy Base85 (oraz dowolne inne kodowanie o podstawie 2-36).

DODATEK

Pamiętacie obrazek z Excelem i formatowaniem warunkowym?

Materiałem dodatkowym będzie “przepis” do CyberChefa w którym pokazuję, jak zawartość pliku graficznego przetworzyć do postaci dającej się bezpośrednio wkleić do Excela. Staram się promować Informatyka Zakładowego na Facebooku i Twitterze, więc ten materiał pojawi się właśnie tam, bo jak już tam zajrzycie, to może i co nieco polajkujecie. Z góry dzięki!

Malutki plik logo.png występujący w instrukcji można pobrać tutaj a wygląda o tak:

Oto bezpośrednie linki do historyjki obrazkowej:
Facebook
Twitter


W tekście użyto skanu niderlandzkiej papierowej pocztówki z roku 1871, która dawno już wpadła do domeny publicznej. Źródło: Wikimedia



O autorze: zawodowy programista od 2003 roku, pasjonat bezpieczeństwa informatycznego. Rozwijał systemy finansowe dla NBP, tworzył i weryfikował zabezpieczenia bankowych aplikacji mobilnych, brał udział w pracach nad grą Angry Birds i wyszukiwarką internetową Microsoft Bing.

7 odpowiedzi na “Wysyłamy plik pocztówką czyli do czego służy kodowanie Base64”

tak, podłączam się. RSS u mnie rządzi 🙂 Fajny artykuł, zrozumiałem jedną rzecz, którą potrzebowałem. Dzięx 😀

Za jakiś tydzień. To będzie poboczny wątek kolejnego tekstu o aplikacjach do walki z koronawirusem.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *