21 Mayıs 2018 Pazartesi

Linux'ta Base64 Kodlaması ve Kullanım Alanı


Merhaba. Bu yazı oldukça basit ama bir yandan da kolaylık sağlayabilecek bir konuyu ele alacağım. Konu base64 kodlaması. Kullanım alanını saydığımda belki bir çok kişiye zorlama gelecek ama pratikte kolaya kaçan çözümler bazen zaman kazandırabiliyor.

Base64 kodlamasının mantığı basit ancak anlatmaya base64'ten başlamayacağım. Bunun yerine tümdengelimci metotla genelden yola çıkıp base64'e geleceğim.

ASCII kod tablosu (wikipedia'dan)
Öncelikle, sanırım herkesin ASCII'nin ne olduğuna dair fikri vardır. ASCII kodlarını genel hatlarıyla akılda tutmak o kadar zor değildir. Akılda tutulması gereken yalnız iki karakter bulunur. Bunlardan ilki, kodu 30h olan '0' karakteridir, ki bunu akılda tutmak kolaydır. Assembly'de veya C'de kod yazanlar BCD sayıları ekrana basmak için 30h eklenmesi gerektiğini hatırlayacaktır. Akılda tutulacak ikinci karakter 41h kodlu 'A' karakteridir. 'A' birinci karakter ve dizinin birinci elemanı olarak düşünüldüğünde sıfırla biten bir kod verilmemesi anlaşılabilir.

İngiliz alfabesinde 26 karakter bulunur. Buna en yakın 2'nin üssü sayı 32'dir. O halde büyük harfler ile küçük harfler arası kolay çevrim için tabloda 'A' ile 'a' arasında 26 boşluk bırakmaktansa 32 (20h) boşluk olması hesaplama kolaylığı sağlar. Rakamlara benzer yaklaşımla büyük harf bir karakter dizisine 20h dizisi eklenirse küçük harfe dönüştürülmüş olur (veya tam tersi). Bunlar akılda kaldıktan sonra 3Xh'lar rakamlar, kabaca 4Xh ile 5Xh'lar büyük harfler ve 6Xh ile 7Xh'lar da küçük harfler olur. Metindeki harfleri ve rakamları çıkardıktan sonra en azından metnin önemli bir bölümü okunabilir duruma gelir. Varsın noktayı, virgülü okuyamayalım.

128 ile 255 arası genişletilmiş ASCII karakterler olarak adlandırılır. Bunlar her kod sayfasında farklıdır. Bu aralık değiştirilebilir karakterleri içerir. Örn. Türkçe karakterler bu kısma yüklenir. 

Tabloya bakıldığında aslında ekrana sorunsuz yazdırılabilecek karakterlerin sağ üst çeyrekteki 64 karakterlik alanda yoğun olarak toplandığı görünmektedir.

Diyelim ki ikilik veri gönderemediğim ama kopyalayıp yapıştırarak veri gönderebildiğim bir iletişim kanalı var. Örn. bir an için dosya göndermenin olmadığı facebook mesajlaşması, WinSCP'ye erişemediğim (veya açmaya üşendiğim) ama SSH oturumu kurulu bir makina örnek verilebilir. "indentation"ın önemli olduğu bir ayar dosyası veya python kodunu facebook'tan göndermek isteyebilirim. Bu kanaldan ikilik dosya göndermek istersem ne yapabilirim?

Yanıt basit: bütün ikilik bitleri bir stream olarak düşünür, bu stream'dan 6 bitlik veriler okurum ve bu verilere 40h eklerim (hatta eklemem, OR'larım) ki basılabilir karakterler elde edeyim. Peki ne kaybedildi? Daha önceden 6 birim olan veri artık 8 birim oldu. Kayıp %33. Bu algoritmalara genel olarak "Binary-to-text encoding" adı veriliyor ve literatürde onlarcası var. Az önce açıkladığım algoritma UUEncode'un dönüştürme algoritmasına benziyor ama aynı değil. UUEncode'da 40h değil 20h ekleniyor ve boşluk karakteriyle başlıyor.

base64 de böyle çalışıyor ama stream, metne dönüştürüleceği zaman sabit bir sayı eklemek yerine tabloyla dönüştürülüyor. Aşağıdaki satır linux'taki base64'ün kaynak kodundan:

static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

Başta sıfıra karşılık A, sonda 63'e karşılık '/' karakteri var. Wikipedia'daki Base64 maddesinde bu tablo bulunuyor. '=' karakterinin özel bir anlamı var. Bitlerin sayısı sekizin tam katı olmadığında sekizin katına tamamlamakta kullanılıyor. 

Bu işlem dosya boyutunu arttırdığından bir kaç kilobyte'dan büyük dosyalar için uygulanabilir olmadığı açık. Bazı durumlarda dosya sıkıştırılıp ardından base64 ile kodlanabilir. Yukarıda da açıkladığım, basılabilir karakterlerin bir aralığa yığılması nedeniyle metin dosyalarının entropileri düşüktür. Bu nedenle de iyi sıkışırlar. Metin dosyalarını sıkıştırıp base64 ile kodlamak yalnızca "indentation"ın korunamadığı durumlar için uygulanabilirdir. 

Örnek için küçük bir dosya olması nedeniyle /bin/dmesg'i seçtim. base64 kodlaması için,

base64 < /bin/dmesg
base64 /bin/dmesg

komutları kullanılabilir. İkisi de aynı sonucu verir. dmesg 6763 byte'lık bir dosya. Dosya base64 ile kodlandığında çıktısı 9103 byte oluyor.

[root@something ~]# ls -la /bin/dmesg
-rwxr-xr-x 1 root root 6736 Oct 15  2014 /bin/dmesg

[root@something ~]# base64  /bin/dmesg | wc -c
9103

9103 / 6736 = 1.351. Yukarıda kaybın %33 olduğunu yazmıştım ama biraz fazla çıktı. Çünkü gözden (bilerek) kaçırdığım bir durum var. base64, çıktıyı standart 80 karakterlik terminal ekrana yazmak için 76 karakterlik satırlara bölüyor. Bu da her 76 karakterde bir '\n' (unix'te line feed - LF) karakteri fazla olması demek. Satır bölme, base64'ün -w parametresiyle kontrol edilir. base64'ün verdiği çıktıyı birleştirelim:

[root@something ~]# base64 -w 0  /bin/dmesg > dmesg.base64
[root@something ~]# cat dmesg.base64 | wc -c
8984

8984 / 6736 = 1.334. Hesap doğru. Çıktıdaki karakterlerin dağılımı aşağıdaki komutla incelenir:

[root@something ~]# base64 -w 1  /bin/dmesg | sort -b | uniq -c

Kontrol karakteri olarak iki tane '=' var. 

Peki kodlanan veri nasıl birleştirilecek? Çıktı, ikili bir dosya olacağından GÇ yönlendirme mutlaka gerekli. base64'ün -d parametresi kodlamayı çözüyor. Komut, girdi olarak dosya veya stdin'i alıyor bu nedenle dosya konsola yazdırılıp komuta yönlendirilebilir veya veri görece küçük olduğundan konsola yapıştırılıp Ctrl+D ile sonlandırılabilir. Aşağıdaki komutlar aynı işi yapmaktadır. Bu arada, her adımda çıktının md5 hash'i kontrol edilerek dosyanın bozulmadığı görülüyor.

[root@something ~]# md5sum /bin/dmesg
e638a28f1d13b71fdcb13500fedcf00d  /bin/dmesg
[root@something ~]# cat dmesg.base64 | base64 -d > dmesg
[root@something ~]# md5sum dmesg
e638a28f1d13b71fdcb13500fedcf00d  dmesg
[root@something ~]# base64 -d dmesg.base64 > dmesg
[root@something ~]# md5sum dmesg
e638a28f1d13b71fdcb13500fedcf00d  dmesg
[root@something ~]# base64 -d < dmesg.base64 > dmesg
[root@something ~]# md5sum dmesg
e638a28f1d13b71fdcb13500fedcf00d  dmesg
[root@something ~]# base64 -d > dmesg
<çıktı buraya yapıştırılır>
Ctrl+D
[root@something ~]# md5sum /bin/dmesg
e638a28f1d13b71fdcb13500fedcf00d  /bin/dmesg

Aynı kodun Windows'ta çözdürülmesi ise şu şekilde yapılmaktadır*:

certutil -decode data.b64 data.txt


Not1: Satır satır bir metnin tek satırda birleştirilmesi paste -sd "" komutuyla da yapılabilir.
Not2: Harflerin histogramı şu komutla da alınabilir*:
od -cvAnone -w1 | sort -b | uniq -c